Repository navigation
Working with Actions
The actions object is the central interface for reading, writing, and reacting to tag state at runtime. It is an instance of AttributeActions and is imported from the device module:
from ethernetip_emulator.server.device import actionsThese low-level methods are available directly on actions and operate on any tag attribute object, regardless of its datatype.
Reads a slice of values from a tag attribute.
| Parameter | Type | Default | Description |
|---|---|---|---|
attr |
tag attribute | — | The attribute object to read from. |
key |
slice |
slice(0, 1) |
The index range to read. |
Returns a list of values, or None if the read fails.
attr = actions._lookup("O_Step")
value = actions._read_attr(attr, slice(0, 1)) # [False]Writes a list of values to a slice of a tag attribute.
| Parameter | Type | Description |
|---|---|---|
attr |
tag attribute | The attribute object to write to. |
key |
slice |
The index range to write. |
value |
list |
The values to write. |
attr = actions._lookup("O_Step")
actions._write_attr(attr, slice(0, 1), [True])Retrieves a tag attribute object from the device registry by name. Returns None if the tag does not exist or the device is not yet bound.
attr = actions._lookup("I_LotNumber")Note: In practice you will rarely need
_read_attr,_write_attr, or_lookupdirectly. Datatype-specific helpers (e.g.actions.bool.set_val(...)) wrap these calls for you. The best use-case for these methods are when creating your own custom datatype.
on_change registers a callback that fires whenever a specific tag is written. It can be used as a decorator or called directly.
actions.<datatype>.on_change(
tag_name: str,
callback=None, # omit to use as a decorator
*,
key: slice = slice(0, 1),
defer: bool = False,
)Every on_change callback receives three arguments:
| Argument | Description |
|---|---|
attr |
The raw tag attribute object. |
key |
The slice that was written. |
value |
A list of the new value(s) at that slice. |
@actions.bool.on_change("O_BOOL")
def handle_bool(attr, key, value) -> None:
if value[0]:
print(f"O_BOOL was set to True")For array tags, key lets you subscribe to a single element or sub-range rather than the whole array. The callback only fires when the written slice matches the key you specified.
# Fires only when index 0 of the array is written
@actions.usintarray.on_change("O_UsintArray", key=slice(0, 1))
def node_1_changed(attr, key, value):
print(f"Node 1 changed: {value[0]}")
# Fires only when index 1 is written
@actions.usintarray.on_change("O_UsintArray", key=slice(1, 2))
def node_2_changed(attr, key, value):
print(f"Node 2 changed: {value[0]}")
# Fires when the whole slice(0, 4) range is written
@actions.usintarray.on_change("O_UsintArray", key=slice(0, 4))
def nodes_changed(attr, key, value):
print(f"All nodes changed: {value}")A listener registered with
key=slice(0, 4)will not fire when onlyslice(0, 1)is written, and vice versa. The keys must match exactly.
By default, on_change callbacks run before the new value is formally committed to the tag. Setting defer=True launches the callback in a background thread, which means it runs after the tag write has completed.
The callback executes inline, before the tag update is logged:
@actions.bool.on_change("O_BOOL")
def handle_bool(attr, key, value) -> None:
if value[0]:
print("On Change Run")Output:
On Change Run
O_BOOL[ 0-0 ] <= [True]
The callback is dispatched to a daemon thread and runs after the tag write is complete:
@actions.bool.on_change("O_BOOL", defer=True)
def handle_bool(attr, key, value) -> None:
if value[0]:
print("On Change Run")Output:
O_BOOL[ 0-0 ] <= [True]
On Change Run
Use defer=True when your callback itself writes back to the same tag or a dependent tag. Because the tag is not yet set when a non-deferred callback runs, writing to it inside a non-deferred handler can cause the write to be overwritten by the original value milliseconds later. A common pattern is toggling a latch:
@actions.bool.on_change("O_BOOL", defer=True)
def handle_bool(attr, key, value) -> None:
if value[0]:
# Safe — the original True write has already been committed
actions.bool.set_val("O_BOOL", False)Without defer=True, the set_val("O_BOOL", False) call would race with the original write and the tag could end up stuck at True.
Both built-in and custom datatypes are accessed through actions.<datatype>. Each datatype exposes its own set of helper methods that internally use _lookup, _read_attr, and _write_attr.
Most datatypes follow a consistent interface:
# Read the current value
actions.bool.get_val("O_BOOL") # → [False]
# Write a new value
actions.bool.set_val("O_BOOL", True)
# Array-specific helpers
actions.boolarray.size("O_BoolArray") # → 4
actions.boolarray.toggle_bit("O_BoolArray", 0) # flip index 0
actions.boolarray.append("O_BoolArray", 1) # add element to end
actions.boolarray.prepend("O_BoolArray", 1) # add element to front
actions.boolarray.clear("O_BoolArray") # reset all elements
# Counter helpers
actions.counter.increment("O_Updates")
actions.counter.is_done("O_Updates") # → bool
# Timer helpers
actions.timer.is_done("O_Timer") # usable as a decorator too
# Increment worker (starts a background auto-increment loop)
actions.increment.start(tag_name="O_INCR", start=0, wrap=actions.int.MAX)