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.


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