Core
pyrung.core
Immutable PLC Engine.
Redux-style architecture where logic is a pure function
Logic(Current_State) -> Next_State
Uses ScanContext for batched updates within a scan cycle, reducing object allocation from O(instructions) to O(1) per scan.
ScanContext
Batched write context for a single scan cycle.
Collects all tag and memory writes during a scan cycle, then commits them all at once to produce a new SystemState. Provides read-after-write visibility so subsequent instructions in the same scan see updated values.
Attributes:
| Name | Type | Description |
|---|---|---|
_state |
The original SystemState (immutable, not modified). |
|
_tags_evolver |
Pyrsistent evolver for final tag commit. |
|
_memory_evolver |
Pyrsistent evolver for final memory commit. |
|
_tags_pending |
dict[str, Any]
|
Fast lookup dict for pending tag writes. |
_memory_pending |
dict[str, Any]
|
Fast lookup dict for pending memory writes. |
original_state
property
Access to the original (unmodified) state.
Useful for operations that need to read original values, such as computing _prev:* for edge detection.
get_tag
Get a tag value, checking pending writes first.
Provides read-after-write visibility within the same scan cycle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The tag name to retrieve. |
required |
default
|
Any
|
Value to return if tag not found. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The tag value from pending writes, original state, or default. |
get_memory
Get a memory value, checking pending writes first.
Provides read-after-write visibility within the same scan cycle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The memory key to retrieve. |
required |
default
|
Any
|
Value to return if key not found. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The memory value from pending writes, original state, or default. |
set_tag
Set a tag value (batched, committed at end of scan).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The tag name to set. |
required |
value
|
Any
|
The value to set. |
required |
set_tags
Set multiple tag values (batched, committed at end of scan).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
updates
|
dict[str, Any]
|
Dict of tag names to values. |
required |
set_memory
Set a memory value (batched, committed at end of scan).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The memory key to set. |
required |
value
|
Any
|
The value to set. |
required |
set_memory_bulk
Set multiple memory values (batched, committed at end of scan).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
updates
|
dict[str, Any]
|
Dict of memory keys to values. |
required |
commit
Commit all pending changes and advance to next scan.
Creates a new SystemState with all batched tag and memory updates, then advances scan_id and timestamp.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dt
|
float
|
Time delta in seconds to add to timestamp. |
required |
Returns:
| Type | Description |
|---|---|
SystemState
|
New SystemState with all changes applied. |
RungTrace
dataclass
Retained per-rung debug trace for one committed scan.
RungTraceEvent
dataclass
One debug event captured for a rung during scan execution.
Expression
Bases: ABC
Base class for all mathematical expressions.
Expressions are lazy-evaluated at scan time against a ScanContext. They can be composed using arithmetic operators and compared to produce Conditions.
evaluate
abstractmethod
Evaluate this expression against a ScanContext.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
ScanContext
|
ScanContext for resolving tag values. |
required |
Returns:
| Type | Description |
|---|---|
Numeric
|
The numeric result of the expression. |
Block
dataclass
Factory for creating Tags from a typed memory region.
Block defines a named, 1-indexed memory region where every address shares
the same TagType. Indexing a Block returns a cached LiveTag. The block
holds no runtime values — all values live in SystemState.tags.
Address bounds are inclusive on both ends: Block("DS", INT, 1, 100)
defines addresses 1–100 (100 tags). Indexing outside this range raises
IndexError. Slice syntax (block[1:10]) is rejected — use
.select(start, end) instead.
For sparse blocks (e.g. Click X/Y banks with non-contiguous valid addresses),
pass valid_ranges to restrict which addresses within [start, end] are
legal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Block prefix used to generate tag names (e.g. |
required |
type
|
TagType
|
|
required |
start
|
int
|
Inclusive lower bound address (must be ≥ 0). |
required |
end
|
int
|
Inclusive upper bound address (must be ≥ start). |
required |
retentive
|
bool
|
Whether tags in this block survive power cycles.
Default |
False
|
valid_ranges
|
tuple[tuple[int, int], ...] | None
|
Optional tuple of |
None
|
address_formatter
|
Callable[[str, int], str] | None
|
Optional callable |
None
|
Example
DS = Block("DS", TagType.INT, 1, 100)
DS[1] # → LiveTag("DS1", TagType.INT)
DS[101] # → IndexError
# Range for block operations:
DS.select(1, 10) # → BlockRange, tags DS1..DS10
# Indirect (pointer) addressing:
idx = Int("Idx")
DS[idx] # → IndirectRef, resolved at scan time
DS[idx + 1] # → IndirectExprRef
rename_slot
Set the first-class logical name for one slot before materialization.
clear_slot_name
Clear a first-class slot name override for one address.
configure_slot
Set per-slot runtime policy before this slot is materialized.
configure_range
configure_range(
start: int,
end: int,
*,
retentive: bool | None = None,
default: object = UNSET,
) -> None
Set per-slot policy for all valid addresses in the inclusive window.
clear_slot_config
Clear per-slot policy overrides for one address.
clear_range_config
Clear per-slot policy overrides for all valid addresses in a window.
slot_config
Return the effective runtime slot policy without materializing a Tag.
select
Select an inclusive range of addresses for block operations.
Both start and end are inclusive: DS.select(1, 10) yields
10 tags (1, 2, … 10). This mirrors the block constructor convention and
avoids the off-by-one confusion of Python's half-open slices.
For sparse blocks (valid_ranges set), returns only the valid addresses
within the window — gaps are silently skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
int | Tag | Any
|
Start address. |
required |
end
|
int | Tag | Any
|
End address. |
required |
Returns:
| Type | Description |
|---|---|
BlockRange | IndirectBlockRange
|
|
BlockRange | IndirectBlockRange
|
definition time). |
BlockRange | IndirectBlockRange
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
IndexError
|
If either bound is outside the block's |
Example
# Static range
DS.select(1, 100) # BlockRange, DS1..DS100
# Sparse window (Click X bank)
x.select(1, 21) # valid tags only: X001..X016, X021
# Dynamic range (resolved each scan)
DS.select(start_tag, end_tag) # IndirectBlockRange
# Use with bulk instructions:
fill(0, DS.select(1, 10))
blockcopy(DS.select(1, 10), DD.select(1, 10))
search(">=", 100, DS.select(1, 100), result=Found, found=FoundFlag)
BlockRange
dataclass
Contiguous range of addresses for block operations.
Attributes:
| Name | Type | Description |
|---|---|---|
block |
Block
|
Source Block. |
start |
int
|
Starting address (inclusive). |
end |
int
|
Ending address (inclusive). |
addresses
property
Return addresses in this block window, filtered by block rules.
IndirectBlockRange
dataclass
Memory block with runtime-resolved bounds.
Wraps a Block with start/end that may be Tags or Expressions, resolved at scan time.
Attributes:
| Name | Type | Description |
|---|---|---|
block |
Block
|
Source Block. |
start_expr |
int | Tag | Any
|
Start address (int, Tag, or Expression). |
end_expr |
int | Tag | Any
|
End address (int, Tag, or Expression). |
resolve_ctx
Resolve expressions to concrete BlockRange using ScanContext.
reverse
Return this same dynamic window with address iteration reversed.
IndirectExprRef
dataclass
Tag with runtime-resolved address via expression.
IndirectExprRef wraps a Block and an Expression. The actual address is computed from the expression at scan time.
This enables pointer arithmetic like DS[idx + 1] where idx is a Tag.
Attributes:
| Name | Type | Description |
|---|---|---|
block |
Block
|
Block to index into. |
expr |
Any
|
Expression whose value determines the address. |
resolve_ctx
Resolve expression value to concrete Tag using ScanContext.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
ScanContext
|
ScanContext to evaluate expression against. |
required |
Returns:
| Type | Description |
|---|---|
Tag
|
Concrete Tag at the computed address. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If resolved address is out of range. |
IndirectRef
dataclass
Tag with runtime-resolved address via pointer.
IndirectRef wraps a Block and pointer Tag. The actual address is resolved from the pointer's value at scan time.
Attributes:
| Name | Type | Description |
|---|---|---|
block |
Block
|
Block to index into. |
pointer |
Tag
|
Tag whose value determines the address. |
resolve
Resolve pointer value to concrete Tag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
SystemState
|
Current system state to read pointer value from. |
required |
Returns:
| Type | Description |
|---|---|
Tag
|
Concrete Tag at the resolved address. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If resolved address is out of range. |
resolve_ctx
Resolve pointer value to concrete Tag using ScanContext.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctx
|
ScanContext
|
ScanContext to read pointer value from. |
required |
Returns:
| Type | Description |
|---|---|
Tag
|
Concrete Tag at the resolved address. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If resolved address is out of range. |
InputBlock
dataclass
Bases: Block
Factory for creating InputTag instances from a physical input memory region.
InputBlock is identical to Block except:
- Indexing returns
LiveInputTag(notLiveTag), so elements have.immediate. - Always non-retentive — physical inputs do not survive power cycles.
Use InputBlock when the tags represent real hardware inputs (sensors,
switches, etc.). In simulation, values are supplied via runner.patch() or
runner.add_force() during the Read Inputs scan phase.
Example
OutputBlock
dataclass
Bases: Block
Factory for creating OutputTag instances from a physical output memory region.
OutputBlock is identical to Block except:
- Indexing returns
LiveOutputTag(notLiveTag), so elements have.immediate. - Always non-retentive — physical outputs do not survive power cycles.
Writes to OutputTag elements are immediately visible to subsequent rungs
within the same scan (standard PLC behavior). The actual hardware write
happens at the Write Outputs scan phase (phase 6).
Example
SlotConfig
dataclass
Effective runtime policy for one block slot.
ForbiddenControlFlowError
Bases: RuntimeError
Raised when Python control flow is used inside strict DSL scope.
ForLoop
Context manager for a repeated instruction block within a rung.
Program
Container for PLC logic (rungs and subroutines).
Used as a context manager to capture rungs
with Program() as logic: with Rung(Button): out(Light)
Also works with PLCRunner
runner = PLCRunner(logic)
call_subroutine
Execute a subroutine by name (legacy state-based API).
call_subroutine_ctx
Execute a subroutine by name within a ScanContext.
register_dialect
classmethod
Register a portability validator callback for a dialect name.
registered_dialects
classmethod
Return registered dialect names in deterministic order.
validate
Run dialect-specific portability validation for this Program.
Rung
SubroutineFunc
A decorated function that represents a subroutine.
Created by using @subroutine("name") as a decorator. When passed to call(), auto-registers with the current Program on first use.
Example
@subroutine("init") def init_sequence(): with Rung(): out(Light)
with Program() as logic: with Rung(Button): call(init_sequence)
PLCRunner
Generator-driven PLC execution engine.
Executes PLC logic as pure functions: Logic(state) -> new_state. The consumer controls execution via step(), enabling: - Input injection via patch() - Inspection of retained historical state via runner.history - Pause/resume at any scan boundary
Attributes:
| Name | Type | Description |
|---|---|---|
current_state |
SystemState
|
The current SystemState snapshot. |
history |
History
|
Query interface for retained SystemState snapshots. |
simulation_time |
float
|
Current simulation clock (seconds). |
time_mode |
TimeMode
|
Current time mode (FIXED_STEP or REALTIME). |
forces
property
Read-only view of active persistent overrides.
rewind
Move playhead backward in time by seconds and return snapshot.
diff
Return changed tag values between two retained historical scans.
inspect
Return retained rung-level debug trace for one scan.
If scan_id is omitted, the current playhead scan is inspected.
Raises:
KeyError: Missing scan id, or missing rung trace for retained scan.
inspect_event
Return the latest debug-trace event for active/committed debug-path scans.
Returns:
| Type | Description |
|---|---|
tuple[int, int, RungTraceEvent] | None
|
A tuple of |
tuple[int, int, RungTraceEvent] | None
|
are preferred when available. Otherwise, the latest retained committed |
tuple[int, int, RungTraceEvent] | None
|
debug-scan event is returned. |
Notes
- This API is populated by
scan_steps_debug()only. - Scans produced through
step()/run()/run_for()/run_until()do not contribute trace events here.
fork
Create an independent runner from retained historical state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scan_id
|
int | None
|
Snapshot to fork from. Defaults to current committed tip state. |
None
|
fork_from
Create an independent runner from a retained historical snapshot.
set_battery_present
Configure simulated backup battery presence.
set_time_mode
Set the time mode for simulation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mode
|
TimeMode
|
TimeMode.FIXED_STEP or TimeMode.REALTIME. |
required |
dt
|
float
|
Time delta per scan (only used for FIXED_STEP mode). |
0.1
|
patch
patch(
tags: Mapping[str, bool | int | float | str]
| Mapping[Tag, bool | int | float | str]
| Mapping[str | Tag, bool | int | float | str],
) -> None
Queue tag values for next scan (one-shot).
Values are applied at the start of the next step() call, then cleared. Use for momentary inputs like button presses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tags
|
Mapping[str, bool | int | float | str] | Mapping[Tag, bool | int | float | str] | Mapping[str | Tag, bool | int | float | str]
|
Dict of tag names or Tag objects to values. |
required |
add_force
Persistently override a tag value until explicitly removed.
The forced value is applied at the pre-logic force pass (phase 3) and re-applied at the post-logic force pass (phase 5) every scan. Logic may temporarily diverge the value mid-scan, but the post-logic pass restores it before outputs are written.
Forces persist across scans until remove_force() or clear_forces()
is called. Multiple forces may be active simultaneously.
If a tag is both patched and forced in the same scan, the force overwrites the patch during the pre-logic pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str | Tag
|
Tag name or |
required |
value
|
bool | int | float | str
|
Value to hold. Must be compatible with the tag's type. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the tag is a read-only system point. |
remove_force
Remove a single persistent force override.
After removal the tag resumes its logic-computed value starting from the next scan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str | Tag
|
Tag name or |
required |
clear_forces
Remove all active persistent force overrides.
All forced tags resume their logic-computed values starting from the next scan.
force
force(
overrides: Mapping[str, bool | int | float | str]
| Mapping[Tag, bool | int | float | str]
| Mapping[str | Tag, bool | int | float | str],
) -> Iterator[PLCRunner]
Temporarily apply forces for the duration of the context.
On entry, saves the current force map and adds the given overrides. On exit (normally or on exception), the exact previous force map is restored — forces that existed before the context are reinstated, and forces added inside the context are removed.
Safe for nesting: inner force() contexts layer on top of outer ones
without disrupting them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
overrides
|
Mapping[str, bool | int | float | str] | Mapping[Tag, bool | int | float | str] | Mapping[str | Tag, bool | int | float | str]
|
Mapping of tag name / |
required |
monitor
Call callback(current, previous) after commit when tag value changes.
when
when(
*conditions: Condition
| Tag
| tuple[Condition | Tag, ...]
| list[Condition | Tag],
) -> _BreakpointBuilder
Create a condition breakpoint builder evaluated after each committed scan.
when_fn
Create a callable-predicate breakpoint builder evaluated after each scan.
scan_steps
Execute one scan cycle and yield after each rung evaluation.
Scan phases: 1. Create ScanContext from current state 2. Apply pending patches to context 3. Apply persistent force overrides (pre-logic) 4. Calculate dt and inject into context 5. Evaluate all logic (writes batched in context), yielding after each rung 6. Re-apply force overrides (post-logic) 7. Batch _prev:* updates for edge detection 8. Commit all changes in single operation
The commit in phase 8 only happens when the generator is exhausted.
scan_steps_debug
Execute one scan cycle and yield ScanStep objects at all boundaries.
Yields a ScanStep at each:
- Top-level rung boundary (
kind="rung") - Branch entry / exit (
kind="branch") - Subroutine call and body steps (
kind="subroutine") - Individual instruction boundaries (
kind="instruction")
Each ScanStep carries source location metadata (source_file,
source_line, end_line), rung enable state, and a trace of
evaluated conditions and instructions.
This is the API used by the DAP adapter. Prefer scan_steps() for
non-debug consumers — it has less overhead and a simpler yield type.
Note
Like scan_steps(), the scan is committed only when the generator
is fully exhausted.
iter_top_level_rungs
Debugger-facing top-level rung iterator.
evaluate_condition_value
Debugger-facing condition evaluation API.
condition_term_text
Debugger-facing condition summary API.
condition_annotation
Debugger-facing annotation API.
condition_expression
Debugger-facing expression rendering API.
run
Execute up to cycles scans, stopping early on pause breakpoints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cycles
|
int
|
Number of scans to execute. |
required |
Returns:
| Type | Description |
|---|---|
SystemState
|
The final SystemState after all cycles. |
run_for
Run until simulation time advances by N seconds or a pause breakpoint fires.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seconds
|
float
|
Minimum simulation time to advance. |
required |
Returns:
| Type | Description |
|---|---|
SystemState
|
The final SystemState after reaching the target time. |
run_until
run_until(
*conditions: Condition
| Tag
| tuple[Condition | Tag, ...]
| list[Condition | Tag],
max_cycles: int = 10000,
) -> SystemState
Run until condition is true, pause breakpoint fires, or max_cycles reached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conditions
|
Condition | Tag | tuple[Condition | Tag, ...] | list[Condition | Tag]
|
|
()
|
max_cycles
|
int
|
Maximum scans before giving up (default 10000). |
10000
|
Returns:
| Type | Description |
|---|---|
SystemState
|
The state that matched the condition, or final state if max reached. |
run_until_fn
run_until_fn(
predicate: Callable[[SystemState], bool],
*,
max_cycles: int = 10000,
) -> SystemState
Run until callable predicate is true, paused, or max_cycles reached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predicate
|
Callable[[SystemState], bool]
|
Callable receiving committed |
required |
max_cycles
|
int
|
Maximum scans before giving up (default 10000). |
10000
|
Returns:
| Type | Description |
|---|---|
SystemState
|
The state that matched the predicate, or final state if max reached. |
SystemState
Bases: PRecord
Immutable snapshot of PLC state at a point in time.
Attributes:
| Name | Type | Description |
|---|---|---|
scan_id |
Monotonically increasing scan counter. |
|
timestamp |
Simulation clock in seconds. |
|
tags |
Immutable mapping of tag names to values (bool, int, float, str). |
|
memory |
Immutable mapping for internal state (timers, counters, etc). |
with_tags
Return new state with updated tags. Original unchanged.
with_memory
Return new state with updated memory. Original unchanged.
AutoDefault
dataclass
Descriptor for per-instance numeric default sequences.
Field
dataclass
Field metadata used by udt and named_array declarations.
InstanceView
1-based indexed view into one structure instance.
Bool
dataclass
Char
dataclass
Dint
dataclass
ImmediateRef
dataclass
InputTag
dataclass
Bases: Tag
Tag representing a physical input channel.
InputTag instances are produced exclusively by indexing an InputBlock.
They add the .immediate property for bypassing the scan-cycle image table.
.immediate semantics by context:
- Simulation (pure): validation-time annotation only; no runtime effect.
- Click dialect: transcription hint for Click software export.
- CircuitPython dialect: generates direct hardware-read code.
- Hardware-in-the-loop: triggers a real hardware read mid-scan.
You cannot create an InputTag directly; use InputBlock[n] instead.
Example
Int
dataclass
OutputTag
dataclass
Bases: Tag
Tag representing a physical output channel.
OutputTag instances are produced exclusively by indexing an OutputBlock.
They add the .immediate property for bypassing the scan-cycle image table.
.immediate semantics by context:
- Simulation (pure): validation-time annotation only; no runtime effect.
- Click dialect: transcription hint for Click software export.
- CircuitPython dialect: generates direct hardware-write code.
- Hardware-in-the-loop: triggers a real hardware write mid-scan.
You cannot create an OutputTag directly; use OutputBlock[n] instead.
Example
Real
dataclass
Tag
dataclass
A reference to a value in SystemState.
Tags define what a value is (name, type, behavior) but hold no runtime state. Values live only in SystemState.tags.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique identifier for this tag. |
type |
TagType
|
Data type (BOOL, INT, DINT, REAL, WORD, CHAR). |
default |
Any
|
Default value (None means use type default). |
retentive |
bool
|
Whether value survives power cycles. |
value
property
writable
Read or write this tag's value through the active runner scope.
Returns the current value as seen by the runner, including any pending
patches or forces. Writes are staged as one-shot patches consumed at
the next step().
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If called outside |
TagType
Bases: Enum
Data types for tags (IEC 61131-3 naming).
Word
dataclass
TimeMode
Bases: Enum
Simulation time modes.
Each scan advances by a fixed dt, regardless of wall clock.
Use for unit tests and deterministic simulations.
Simulation clock tracks actual elapsed time.
Use for integration tests and hardware-in-loop.
TimeUnit
lro
Rotate left function (16-bit): lro(value, count).
lsh
Left shift function: lsh(value, count).
rro
Rotate right function (16-bit): rro(value, count).
rsh
Right shift function: rsh(value, count).
all_of
all_of(
*conditions: Condition
| Tag
| ImmediateRef
| tuple[Condition | Tag | ImmediateRef, ...]
| list[Condition | Tag | ImmediateRef],
) -> AllCondition
AND condition - true when all sub-conditions are true.
This is equivalent to comma-separated rung conditions, but useful when building
grouped condition trees with any_of() or &.
Example
with Rung(all_of(Ready, AutoMode)): out(StartPermissive)
Equivalent operator form:
with Rung((Ready & AutoMode) | RemoteStart): out(StartPermissive)
any_of
OR condition - true when any sub-condition is true.
Use this to combine multiple conditions with OR logic within a rung. Multiple conditions passed directly to Rung() are ANDed together.
Example
with Rung(Step == 1, any_of(Start, CmdStart)): out(Light) # True if Step==1 AND (Start OR CmdStart)
Also works with | operator:
with Rung(Step == 1, Start | CmdStart): out(Light)
Grouped AND inside OR (explicit):
with Rung(any_of(Start, all_of(AutoMode, Ready), RemoteStart)): out(Light)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conditions
|
Condition | Tag | ImmediateRef
|
Conditions to OR together. |
()
|
Returns:
| Type | Description |
|---|---|
AnyCondition
|
AnyCondition that evaluates True if any sub-condition is True. |
blockcopy
Block copy instruction.
Copies values from source BlockRange to dest BlockRange. Both ranges must have the same length.
Example
with Rung(CopyEnable): blockcopy(DS.select(1, 10), DD.select(1, 10))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Any
|
Source BlockRange or IndirectBlockRange from .select(). |
required |
dest
|
Any
|
Dest BlockRange or IndirectBlockRange from .select(). |
required |
oneshot
|
bool
|
If True, execute only once per rung activation. |
False
|
branch
Create a parallel branch within a rung.
A branch executes when both the parent rung conditions AND the branch's own conditions are true.
Example
with Rung(Step == 0): out(Light1) with branch(AutoMode): # Only executes if Step==0 AND AutoMode out(Light2) copy(1, Step, oneshot=True)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conditions
|
ConditionTerm
|
Conditions that must be true (in addition to parent rung) for this branch's instructions to execute. |
()
|
Returns:
| Type | Description |
|---|---|
Branch
|
Branch context manager. |
calc
Calc instruction.
Evaluates an expression and stores the result in dest, with truncation to the destination tag's bit width (modular wrapping).
Key differences from copy(): - Truncates result to destination tag's type width - Division by zero produces 0 (not infinity) - Infers decimal/hex behavior from referenced tag types
Example
with Rung(Enable): calc(DS1 * DS2 + DS3, Result) calc(MaskA & MaskB, MaskResult)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expression
|
Any
|
Expression, Tag, or literal to evaluate. |
required |
dest
|
Tag
|
Destination tag (type determines truncation width). |
required |
oneshot
|
bool
|
If True, execute only once per rung activation. |
False
|
Returns:
| Type | Description |
|---|---|
Tag
|
The dest tag. |
call
Call a subroutine instruction.
Executes the named subroutine when the rung is true. Accepts either a string name or a @subroutine-decorated function.
Example
with Rung(Button): call("init_sequence")
with subroutine("init_sequence"): with Rung(): out(Light)
Or with decorator:
@subroutine("init") def init_sequence(): with Rung(): out(Light)
with Program() as logic: with Rung(Button): call(init_sequence)
copy
copy(
source: Any,
target: Tag | IndirectRef | IndirectExprRef,
oneshot: bool = False,
) -> Tag | IndirectRef | IndirectExprRef
Copy instruction (CPY/MOV).
Copies source value to target.
Example
with Rung(Button): copy(5, StepNumber)
count_down
Count Down instruction (CTD) - Click-style.
Creates a counter that decrements every scan while the rung condition is True.
Use rise() on the condition for edge-triggered counting.
Example
with Rung(rise(Dispense)): count_down(done_bit, acc, preset=25).reset(Reload)
This is a terminal instruction. Requires .reset() chaining.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
done_bit
|
Tag
|
Tag to set when accumulator <= -preset. |
required |
accumulator
|
Tag
|
Tag to decrement while rung condition is True. |
required |
preset
|
Tag | int
|
Target value (Tag or int). |
required |
Returns:
| Type | Description |
|---|---|
CountDownBuilder
|
Builder for chaining .reset(). |
count_up
Count Up instruction (CTU) - Click-style.
Creates a counter that increments every scan while the rung condition is True.
Use rise() on the condition for edge-triggered counting.
Example
with Rung(rise(PartSensor)): count_up(done_bit, acc, preset=100).reset(ResetBtn)
This is a terminal instruction. Requires .reset() chaining.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
done_bit
|
Tag
|
Tag to set when accumulator >= preset. |
required |
accumulator
|
Tag
|
Tag to increment while rung condition is True. |
required |
preset
|
Tag | int
|
Target value (Tag or int). |
required |
Returns:
| Type | Description |
|---|---|
CountUpBuilder
|
Builder for chaining .down() and .reset(). |
fall
Falling edge contact (FE).
True only on 1->0 transition. Requires PLCRunner to track previous values.
Example
with Rung(fall(Button)): reset(MotorRunning) # Resets when button is released
fill
Fill instruction.
Writes a constant value to every element in a BlockRange.
Example
with Rung(ClearEnable): fill(0, DS.select(1, 100))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
Any
|
Value to write (literal, Tag, or Expression). |
required |
dest
|
Any
|
Dest BlockRange or IndirectBlockRange from .select(). |
required |
oneshot
|
bool
|
If True, execute only once per rung activation. |
False
|
forloop
Create a repeated instruction block context.
Example
with Rung(Enable): with forloop(10) as loop: copy(Source[loop.idx + 1], Dest[loop.idx + 1])
latch
Latch/Set instruction (SET).
Sets target to True. Unlike OUT, does NOT reset when rung goes false. Use reset() to turn off.
Example
with Rung(StartButton): latch(MotorRunning) latch(C.select(1, 8))
off_delay
off_delay(
done_bit: Tag,
accumulator: Tag,
*,
preset: Tag | int,
unit: TimeUnit = TimeUnit.Tms,
) -> OffDelayBuilder
Off-Delay Timer instruction (TOF) - Click-style.
Done bit is True while enabled. After disable, counts until preset, then done bit goes False. Auto-resets when re-enabled.
Example
with Rung(MotorCommand): off_delay(done_bit, acc, preset=10000)
Off-delay timers are composable in-rung (not terminal).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
done_bit
|
Tag
|
Tag that stays True for preset time after rung goes false. |
required |
accumulator
|
Tag
|
Tag to increment while disabled. |
required |
preset
|
Tag | int
|
Delay time in time units (Tag or int). |
required |
unit
|
TimeUnit
|
Time unit for accumulator (default: Tms). |
Tms
|
Returns:
| Type | Description |
|---|---|
OffDelayBuilder
|
Builder for the off_delay instruction. |
on_delay
on_delay(
done_bit: Tag,
accumulator: Tag,
*,
preset: Tag | int,
unit: TimeUnit = TimeUnit.Tms,
) -> OnDelayBuilder
On-Delay Timer instruction (TON/RTON) - Click-style.
Accumulates time while rung is true.
Example
with Rung(MotorRunning): on_delay(done_bit, acc, preset=5000) # TON on_delay(done_bit, acc, preset=5000).reset(ResetBtn) # RTON
Without .reset(), this is TON and remains composable in-rung. With .reset(), this is RTON and becomes terminal in the current flow.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
done_bit
|
Tag
|
Tag to set when accumulator >= preset. |
required |
accumulator
|
Tag
|
Tag to increment while enabled. |
required |
preset
|
Tag | int
|
Target value in time units (Tag or int). |
required |
unit
|
TimeUnit
|
Time unit for accumulator (default: Tms). |
Tms
|
Returns:
| Type | Description |
|---|---|
OnDelayBuilder
|
Builder for optional .reset() chaining. |
out
out(
target: Tag | BlockRange | ImmediateRef,
oneshot: bool = False,
) -> Tag | BlockRange | ImmediateRef
Output coil instruction (OUT).
Sets target to True when rung is true. Resets to False when rung goes false.
Example
with Rung(Button): out(Light) out(Y.select(1, 4))
pack_bits
Pack BOOL tags from a BlockRange into a register destination.
pack_text
pack_text(
source_range: Any,
dest: Any,
*,
allow_whitespace: bool = False,
oneshot: bool = False,
) -> None
Pack Copy text mode: parse a TXT/CHAR range into a numeric destination.
pack_words
Pack two 16-bit tags from a BlockRange into a 32-bit destination.
reset
Reset/Unlatch instruction (RST).
Sets target to its default value (False for bits, 0 for ints).
Example
with Rung(StopButton): reset(MotorRunning) reset(C.select(1, 8))
return_early
Return from the current subroutine.
Example
with subroutine("my_sub"): with Rung(Abort): return_early()
rise
Rising edge contact (RE).
True only on 0->1 transition. Requires PLCRunner to track previous values.
Example
with Rung(rise(Button)): latch(MotorRunning) # Latches on button press, not while held
run_enabled_function
run_enabled_function(
fn: Callable[..., dict[str, Any]],
ins: dict[
str, Tag | IndirectRef | IndirectExprRef | Any
]
| None = None,
outs: dict[str, Tag | IndirectRef | IndirectExprRef]
| None = None,
) -> None
Execute a synchronous function every scan with rung enabled state.
run_function
run_function(
fn: Callable[..., dict[str, Any]],
ins: dict[
str, Tag | IndirectRef | IndirectExprRef | Any
]
| None = None,
outs: dict[str, Tag | IndirectRef | IndirectExprRef]
| None = None,
*,
oneshot: bool = False,
) -> None
Execute a synchronous function when rung power is true.
search
search(
condition: str,
value: Any,
search_range: BlockRange | IndirectBlockRange,
result: Tag,
found: Tag,
continuous: bool = False,
oneshot: bool = False,
) -> Tag
Search instruction.
Scans a selected range and writes the first matching address into result.
Writes found True on hit; on miss writes result=-1 and found=False.
shift
Shift register instruction builder.
Data input comes from current rung power. Use .clock(...) then .reset(...) to finalize and add the instruction.
Example
with Rung(DataBit): shift(C.select(2, 7)).clock(ClockBit).reset(ResetBit)
subroutine
Define a named subroutine.
Subroutines are only executed when called via call(). They are NOT executed during normal program scan.
Example
with Program() as logic: with Rung(Button): call("my_sub")
with subroutine("my_sub"):
with Rung():
out(Light)
unpack_to_bits
Unpack a register source into BOOL tags in a BlockRange.
unpack_to_words
Unpack a 32-bit register source into two 16-bit tags in a BlockRange.
auto
Create a per-instance numeric default sequence descriptor.
named_array
named_array(
base_type: object, *, count: int = 1, stride: int = 1
) -> Callable[[type[Any]], _NamedArrayRuntime]
Decorator that builds a single-type, instance-interleaved structured runtime.
udt
Decorator that builds a mixed-type structured runtime from annotations.