Attributes
Attributes are the state variables of a simulation model. Each attribute exposes two synchronized views of its value:
internal_value: the authoritative value used inside the simulation (physics, control logic, actions).external_value: the value visible to the outside world (protocol adapters, test harnesses, UI). When you setexternal_value, hooks fire and you can override the internal state temporarily.
Every attribute lives inside an attributes container (SpxAttributes). This container creates one SpxAttribute child per entry and gives you convenient mapping interfaces for read/write access.
Declaring attributes
attributes:
temperature:
type: float
default: 25.0
unit: "C"
heater_on:
type: bool
default: false
device_label: "SPX-001" # scalar shortcut -> type inferred as string{
"attributes": {
"temperature": { "type": "float", "default": 25.0, "unit": "C" },
"heater_on": { "type": "bool", "default": false },
"device_label": "SPX-001"
}
}typemust be one of the supported entries intype_mapping(float,bool,int,str).defaultseedsinternal_value. If omitted the SDK falls back to the type default.A scalar value is shorthand for
{type: inferred, default: value}.Any extra keys (for example
unit, custom metadata) stay on the attribute object and can be consumed later.
Accessing values at runtime
The SpxAttributes container exposes two mapping-like helpers:
These helpers delegate to each child's internal_value or external_value. Setting external_value triggers hooks (on_set, on_external_set). Changing internal_value resets the cached external override so the internal value becomes visible again.
You can work with wrappers directly:
Wrappers are useful when you pass attribute handles into actions, hooks, or other components.
Linking to component properties
Attributes can mirror properties or methods on a domain object (for example a hardware emulator). Use the link helpers to keep the simulation in sync with your backing implementation.
link_to_internal_property(instance, property_name)andlink_to_internal_func(instance, getter, setter)synchronize internal values and reset external overrides.link_to_external_propertyandlink_to_external_funcperform the same job for the external side.unlink_internal_property()andunlink_external_property()break the link.
Internally the SDK wraps the target property or method in a LinkedProperty. Tests in tests/test_attributes/test_spx_attribute.py cover these flows.
Searching and resolving attributes
When you need to look up attributes dynamically there are several helpers in spx_sdk.attributes.resolve_attribute:
is_attribute_reference(ref): detect#attr(...),$external(...), dotted paths (instance.attr) and return the cleaned path plus whether it is internal or external.resolve_attribute_reference(instance, ref): resolve a reference relative toinstanceand return an internal or external wrapper.resolve_attribute_reference_hierarchical(instance, ref): walk up the component tree until the attribute is found.substitute_attribute_references_hierarchical(instance, text): replace all references in a string with literal values. Used by actions and conditions before callingeval.extract_attribute_wrappers_hierarchical(instance, text)andsubstitute_with_wrappers(text, wrappers): collect wrappers for later substitution, handy when building custom evaluators.resolve_nested_chain_reference(instance, "$( .root.subsystem.attributes.voltage.internal_value )"): follow dotted paths starting from the system root and return aStaticAttributeWrapper.
Example:
Using attribute references in definitions
Attribute references appear throughout the SDK. The prefix indicates which wrapper to use:
#attr(name)or#internal(name): read/write the internal value.#external(name)or#ext(name): read/write the external value (what protocols see).$in(...)/$out(...): historical aliases used by protocol and action builders.$(.path.to.attribute): nested chain from the root; returns a static wrapper (seeresolve_nested_chain_reference).
Actions, conditions, and custom components rely on these patterns. The diagnostics in tests/test_actions/test_actions.py and tests/test_attributes/test_resolve_attributes.py illustrate the end to end behaviour.
Hooks on value changes
Each attribute can trigger hooks on specific events:
on_set: fires for any change (internal_valueorexternal_value).on_internal_set: fires only wheninternal_valuechanges.on_external_set: fires only whenexternal_valuechanges.
Declare hooks just like in other components:
The Hooks container (see the hooks chapter) registers and executes these hook components automatically.
Best practices
Treat
internal_valueas the simulation truth. Useexternal_valuefor overrides, protocol requests, and UI input. Remember that settinginternal_valueclears the external override.Prefer the
attributes.internalandattributes.externalviews for quick reads and writes; drop down to wrappers only when you need persistent handles.Use helper functions (
is_attribute_reference,resolve_attribute_reference_hierarchical) in your custom components rather than parsing references manually.When linking to real objects, ensure getters and setters are cheap and side-effect free; they will be called during every prepare/run cycle.
Keep attribute expressions simple. For heavier computations consider using actions or custom components that consume attribute wrappers.
Last updated

