Using spx-python for Simulation

spx-python is a Pythonic wrapper around the SPX Server API. It exposes the SPX system as a dictionary-like tree so you can build, control, and inspect simulations directly from Python. With spx-python you can:

  • Create models and instantiate them,

  • Read/write attributes (internal/external),

  • Control simulation time deterministically,

  • Run Model-in-the-Loop (MiL) tests, inject faults, and verify outcomes.

Key idea: your production app (SUT) talks to the simulation over the real protocol (MQTT/HTTP/…); meanwhile your tests use spx-python as a control channel to set parameters, advance time, and check results.


Purpose

spx-python provides deterministic, programmable control over SPX simulations:

  • Build systems programmatically (models → instances → connections),

  • Manipulate parameters and states during execution,

  • Advance time explicitly for reproducible scenarios,

  • Automate MiL tests and edge-case validation,

  • Prototype scenarios quickly without hardware.


Installation and Setup

Install from PyPI:

Connect to a running SPX Server:


Core Concepts Recap (client view)

  • Models – reusable templates. Create/replace with: client["models"]["MyModel"] = {...}

  • Instances – live objects created from models: client["instances"]["sensor"] = "MyModel"

  • Attributes – named values on instances, accessed via: inst["attributes"]["temperature"].internal_value (read/write) inst["attributes"]["temperature"].external_value (read/write)

  • Connections – links between attributes across instances: client["connections"]["c1"] = {"from": "#ext(a.b)", "to": "#in(c.d)"}

  • Time & run loop – you advance time and run explicitly: inst["timer"]["time"] = t; client.prepare(); client.run()


Quick Start Example (end‑to‑end)

Minimal heater model that raises temperature based on power:

What to notice

  1. You assign a Python dict to client["models"]["Heater"].

  2. You instantiate by assigning the model name under client["instances"].

  3. You set inputs via internal_value or external_value.

  4. You drive time through instance["timer"]["time"] and call client.run().

  5. You read outputs from external_value.


Attribute Access Cheat Sheet

Conventionally, inputs are written to internal_value and reported/observable values are read from external_value. Your model can map them as needed.


Multiple Instances and Connections

Create two instances and wire them together:


Using spx-python in MiL Tests (pytest)

Deterministic MiL test that advances time and asserts behavior:

Your SUT can run alongside this test and communicate with the model over the real protocol. The test remains in full control of time and parameters via spx-python.


LLM Integration and Test Logging Patterns

When you drive SPX with LLM-powered tooling or build test automation, keep these patterns in mind:

  • Treat the client as a dictionary-like tree (models, instances, attributes).

  • Keep secrets out of code and examples: set SPX_PRODUCT_KEY (and optionally SPX_BASE_URL) in the environment.

  • Prefer helper functions in spx_python.helpers for idempotent setup and polling (for example: load_model, ensure_model, create_instance, wait_for_attribute_value, wait_for_state).

  • For structured test logging, use the helper utilities (for example: spx_log_test_case, spx_append_attribute_value) instead of rolling your own.

  • Integration tests should skip when SPX_PRODUCT_KEY is missing or the server is unavailable.

Minimal pytest skip guard:


Fault Injection and Edge Cases

Inject a sensor fault mid-run and verify system response:


Hot‑Reloading Components (server endpoint)

If your server exposes an admin reload endpoint (e.g., system.reload), call it before creating new instances to make freshly registered components available. spx-python focuses on the system tree; for custom admin endpoints use requests:


Best Practices

  • Deterministic time: always set instance["timer"]["time"] before client.run(). Keep timer.dt consistent for repeatability.

  • Small, focused models: compose simple units for clearer tests and faster iterations.

  • Separate channels: let the SUT use the protocol channel; keep spx-python for control/observation.

  • Version scenarios: store model defs and test seeds in your repo for stable CI.


Further Reading

Last updated