Skip to content

Programming STP Sims with Python

Connor Jakubik edited this page Oct 14, 2026 · 10 revisions
Simulation Development Workflow → Programming STP Sims with Python

Up to date for Platform 0.54.2

Written by Neil McHenry and Connor Jakubik.

Prerequisites:


Note

This page explains how to author and run custom Python systems that interact with live simulations. For initial state configuration and JSON syntax, review The Sim Config. For parameter typing details, review The Parameter.

Programming STP Sims with Python

1. Overview: Systems in Space Teams PRO

In Space Teams PRO, simulation tasks are organized cleanly into State and Behavior:

  • Entities (State): Everything that exists spatially in a simulation (spacecraft, celestial bodies, robotic arms, sensor masts) is an Entity. Entities store their state as parameters in a ParamMap. Any parameter change on an Entity is replicated across the network to all connected clients.
  • Systems (Behavior): Logic that evaluates algorithms, calculates mathematical equations, reads inputs, and drives Entity parameters over time.
┌────────────────────────────────────────────────────────┐
│                   Space Teams PRO                      │
│                                                        │
│   ┌────────────────────┐      ┌────────────────────┐   │
│   │    Rover Entity    │      │    Planet Entity   │   │
│   │ (Location, State)  │      │  (BodyFixedFrame)  │   │
│   └─────────▲──────────┘      └─────────▲──────────┘   │
│             │                           │              │
│       Param Access                Frame Query          │
│             │                           │              │
│   ┌─────────┴───────────────────────────┴──────────┐   │
│   │       Example Python System Process            │   │
│   │    - Reads Config Inputs                       │   │
│   │    - Steps Kinematic Loop                      │   │
│   │    - Updates Entity Location & Rotation        │   │
│   │    - Emits HUD Alerts and Logs                 │   │
│   └────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────┘

Systems can be written as compiled C++ shared libraries (.dll or .so) or as standalone Python scripts (.py). When a simulation starts, Compute_Server launches configured Python systems as separate processes and connects them directly to the simulation runtime.

Important

Replication Rule: Only Entities have automatic parameter replication across the network. System instance parameters do not replicate. Systems are designed to execute locally, updating entity parameters that the platform core then synchronizes across all users. So, don't use System instance parameters to store information that needs to be saved mid-sim or that may be required by another portion of the sim.


2. Python System Lifecycle and Boilerplate

Every Space Teams Python system must open with the standard connection and sim start boilerplate. Follow the instructions here on making a new Python System then come back.

Here is the top section of Python Template boilerplate code from v0.54.2:

import os, sys, time, datetime, traceback
import spaceteams as st
import numpy as np
# Can import other modules here, but any code that actually executes before st.connect_to_sim() 
# will have less helpful logging and error handling. SimGlobals and logger functions are not yet available.

def before_init():
    pass # Replace this to run code during the Sync state that needs to finish before Init starts anywhere in the sim.
    # Sync state is where Sim Editing is done, so code in here shouldn't change parameters that would
    # make saving and reloading the sim problematic.

def before_runtime():
    pass # Replace this to run code during the Init state that needs to finish before Runtime starts anywhere in the sim.

#################################################################
# DON'T CHANGE ANY OF THE BELOW; NECESSARY FOR JOINING SIMULATION
def custom_exception_handler(exctype, value, tb):
    error_message = "".join(traceback.format_exception(exctype, value, tb))
    st.logger_fatal(error_message)
    exit(1)
sys.excepthook = custom_exception_handler
st.BeforeInit(before_init)
st.BeforeRuntime(before_runtime)
st.connect_to_sim(sys.argv)
# DON'T CHANGE ANY OF THE ABOVE; NECESSARY FOR JOINING SIMULATION
#################################################################

This boilerplate code is explained in the Make New Python System wiki page. Once the code has passed the st.connect_to_sim line, it is part of the running simulation.


3. Querying System Parameters and Entity References

A Python system accesses its own configuration settings and references to specific entities using st.GetThisSystem().

this = st.GetThisSystem()

# Read per-system-instance parameters configured in the SimConfig
loop_rate = this.GetParam(st.VarType.double, "LoopFreqHz")
drive_speed = this.GetParam(st.VarType.double, "DriveSpeed")
enable_feature = this.GetParam(st.VarType.bool, "EnableFeature")

# Retrieve Entity references from instance parameters in the SimConfig
rover_entity: st.Entity = this.GetParam(st.VarType.entityRef, "TargetEntity")
planet_entity: st.Entity = this.GetParam(st.VarType.entityRef, "PlanetRef")

If entities have the Python System applied to them, instead of being individually referenced by an EntityRef parameter, they can be retrieved with st.get_entities().

entities = st.get_entities()
for en in entities:
    st.logger_info(en.getName())

Common st.VarType Parameter Types

Enum Python Type Description
st.VarType.bool bool Boolean value (True or False)
st.VarType.int32 int 32-bit signed integer
st.VarType.double float Standard 64-bit floating point number
st.VarType.string str Text string
st.VarType.doubleV3 np.ndarray (3,) 3D coordinate vector [x, y, z]
st.VarType.doubleV4 np.ndarray (4,) 4D vector or quaternion [x, y, z, w]
st.VarType.entityRef st.Entity Reference pointer to a simulation entity

See the full set of Python parameter types on the Python API wiki page.

Tip

In C++, EntityRef parameters return a SC_EntityRef object that must be .lock()ed to access the Entity inside it. In the Python API, this.GetParam(st.VarType.entityRef, ...) handles reference locking internally, returning an owning st.Entity reference. This has special significance when entities are deleted, but their owning references keep their data alive.


4. Entity Positioning and Spatial Motion

Space Teams uses a hierarchy of reference frames to manage parent-child spatial relationships. Input or output of entity 3D state is always relative to a frame. Commonly, one uses a Body-Fixed Frame of another entity as the frame.

Reading Entity Location

# Obtain reference frames
planet_frame = planet_entity.GetBodyFixedFrame()

# Retrieve location expressed in the planet's coordinate frame
current_loc = rover_entity.getLocation().WRT_ExprIn(planet_frame)
# WRT_ExprIn means "With Respect To, and Expressed in"
print(f"Current coordinates (m): X={current_loc[0]}, Y={current_loc[1]}, Z={current_loc[2]}")

This code means: "current_loc is the location of the rover entity with respect to the planet body fixed frame, expressed as a vector with x,y,z components in the planet body fixed frame."

Updating Entity Position and Kinematics

To translate an entity through space, compute the target coordinates and apply them using one of the following methods:

# Option A: Direct Location Update (Kinematic Teleport / Prescribed Motion)
new_loc = current_loc + np.array([5.0, 0.0, 0.0])
rover_entity.setLocation(st.frames.FramedLoc(new_loc, planet_frame))

# Option B: Set Location and Zero-out Velocity and Acceleration in the given frame
rover_entity.setLocation_ZeroVel(st.frames.FramedLoc(new_loc, planet_frame))

# Option C: Set Location, Velocity, and Acceleration Simultaneously
vel_vector = np.array([2.5, 0.0, 0.0])
acc_vector = np.array([0.0, 0.0, 0.0])
rva_data = st.frames.rva_struct(new_loc, vel_vector, acc_vector)
rover_entity.setLocVelAcc(st.frames.FramedLocVelAcc(rva_data, planet_frame))

Updating Entity Orientation (Rotation)

Orientations are applied using FramedRot objects constructed from Direction Cosine Matrices (DCM):

# Construct a rotation matrix (3x3 DCM) or quaternion
dcm_matrix = np.identity(3)
rover_entity.setRotation(st.frames.FramedRot(dcm_matrix, planet_frame))

We have a set of rotation formalism conversion functions in the st.math namespace, such as st.math.Quat_to_DCM(), st.math.DCM_to_Euler321(), and the other combinations.


5. User Feedback: Alerts and Notifications

Space Teams provides built-in mechanisms to present information directly to users inside the simulation viewport:

# On-Screen Alert: Temporary banner across user viewports (auto-dismisses)
st.OnScreenAlert(
    "Target waypoint reached!", 
    "NavigationTopic", 
    st.Severity.Info
)

# On-Screen Log Message: Appends entry into persistent HUD message feed
st.OnScreenLogMessage(
    "Thruster subsystem initialized.", 
    "Subsystems", 
    st.Severity.Info
)

# Logger output to Compute_Server console and session logs
st.logger_info("Step completed successfully.")

Severity levels are controlled by the st.Severity enum: Info, Warning, Error, Fatal, and Chat. Fatal will, for some of the functions, immediately stop the sim with the output as the error message.


6. Complete Example: Prescribed Circular Motion System

Below is a complete, standalone Python system that moves an entity in a horizontal circle around an origin point while logging progress:

import os, sys, time, datetime, traceback
import spaceteams as st
import numpy as np
# Can import other modules here, but any code that actually executes before st.connect_to_sim() 
# will have less helpful logging and error handling. SimGlobals and logger functions are not yet available.

def before_init():
    pass # Replace this to run code during the Sync state that needs to finish before Init starts anywhere in the sim.
    # Sync state is where Sim Editing is done, so code in here shouldn't change parameters that would
    # make saving and reloading the sim problematic.

def before_runtime():
    pass # Replace this to run code during the Init state that needs to finish before Runtime starts anywhere in the sim.

#################################################################
# DON'T CHANGE ANY OF THE BELOW; NECESSARY FOR JOINING SIMULATION
def custom_exception_handler(exctype, value, tb):
    error_message = "".join(traceback.format_exception(exctype, value, tb))
    st.logger_fatal(error_message)
    exit(1)
sys.excepthook = custom_exception_handler
st.BeforeInit(before_init)
st.BeforeRuntime(before_runtime)
st.connect_to_sim(sys.argv)
# DON'T CHANGE ANY OF THE ABOVE; NECESSARY FOR JOINING SIMULATION
#################################################################


st.OnScreenLogMessage("CircularMotionSystem starting...", "Navigation", st.Severity.Info)

# 1. Grab configuration parameters and entities
this = st.GetThisSystem()

target_entity: st.Entity = this.GetParam(st.VarType.entityRef, "ControlledEntity")
center_ref: st.Entity = this.GetParam(st.VarType.entityRef, "CenterFrameEntity")
reference_frame = center_ref.GetBodyFixedFrame()

# Motion parameters
radius = 25.0         # Orbit radius (meters)
angular_speed = 0.2   # Yaw rotation speed (rad/s)
loop_freq = this.GetParam(st.VarType.double, "LoopFreqHz")      # Target frequency (Hz)
dt = 1.0 / loop_freq

st.OnScreenAlert("Entity autonomous movement engaged.", "Motion", st.Severity.Info)

time_prev = st.SimGlobals.SimClock.GetTimeNow()
time_now = time_prev

theta = 0.0
while True:
    # Update sim timing
    time_now = st.SimGlobals.SimClock.GetTimeNow()
    dt = time_now - time_prev
    time_prev = time_now

    # 2. Compute circular trajectory coordinates
    x = radius * np.cos(theta)
    y = radius * np.sin(theta)
    z = 0.0  # Planar elevation

    # 3. Compute tangent velocity vector
    vx = -radius * angular_speed * np.sin(theta)
    vy =  radius * angular_speed * np.cos(theta)
    vz = 0.0

    new_pos = np.array([x, y, z])
    vel_vec = np.array([vx, vy, vz])
    acc_vec = np.array([0.0, 0.0, 0.0]) # of course, real circular motion has acceleration, and that could be calculated

    # 4. Commit kinematic update to entity
    kinematic_state = st.frames.rva_struct(new_pos, vel_vec, acc_vec)
    target_entity.setLocVelAcc(st.frames.FramedLocVelAcc(kinematic_state, reference_frame))

    # 5. Advance phase angle and maintain execution pacing
    theta += angular_speed * dt
    if theta >= 2.0 * np.pi:
        theta -= 2.0 * np.pi

    # Note that this loop frequency does not track with possible timescale in the sim
    time.sleep(1.0 / loop_freq)

7. Connecting the System in Your Sim Config

To run your Python script within a simulation, register it inside your .simconf file under SystemManager -> Systems:

{
  "Nametag": "CircularMotionSystem",
  "Source": "Local/Repos/MyRepo/CircularMotionSystem.py",
  "Type": "Python",
  "Active": true,
  "Frequency": 50.0,
  "Inst_Parameters": {
    "ControlledEntity": [ "EntityRef", "Excavator_1" ],
    "CenterFrameEntity": [ "EntityRef", "Moon" ],
    "DriveSpeed": 2.5
  }
}

All EntityRef parameters must reference an actual entity in the sim, or its included sims.

  • Nametag: Unique system identifier.
  • Source: Path to your Python file (relative to your installation or local assets directory).
  • Type: Must be set to "Python".
  • Inst_Parameters: Defines initial parameters queried by this.GetParam() inside your script.

8. Development Best Practices

  • Always Include Loop Sleeping: Ensure your update loop includes time.sleep(1.0 / frequency), or something else like a UI library is handling the update rate. Without pacing, Python will consume 100% of a CPU core.
  • Throttle Screen Messages: Do not call st.OnScreenAlert() or st.OnScreenLogMessage() every frame in high-frequency loops; rate-limit notifications to avoid filling HUD message queues.
  • Installing Third-Party Libraries: If your system imports external packages that we don't include by default (like torch or rasterio), install them into Space Teams PRO's automatically generated virtual environment. We have a standard way of doing this:
    • On Windows:
      • Open Terminal in the <YOUR STP INSTALL DIR>\Compute_Server folder, and run this command:
        .\st_pip.bat install package_name
    • On Linux:
      • source the python virtual environment at
        ~/.local/share/SimDynamX/STPro/Versions/v<STP VERSION HERE>/py
      • pip install package_name

Next Steps

  1. Consult the Python API Docs for functionality details.

Clone this wiki locally