STU Lunar SAR API Docs - SimDynamX/SpaceTeamsPRO GitHub Wiki

Up to date for Platform 0.27.5 and STU Lunar SAR 1.3.2

Written by Neil McHenry and Connor Jakubik

Prerequisites:


Rules & Info Setup & Learning STU API Docs (Here) Q&A Discussions Category Starter Code Starter Code Releases

Changelog:

  • 2024/11/19 Many changes for Task 3, including new API functions and revised gaussian speed vs slope curves.
  • 2024/11/09 Updates for Platform 0.27.5.
  • 2024/11/08 Documentation for GetStateOfCharge and GetChargingStationXY Entity Telemetry functions.
  • 2024/11/06 Small updates for Task 2.
  • 2024/11/04 Added Slope vs. Power and Slope vs. Speed Curves.
  • 2024/10/23 Newly created.

Link back to STU 2024 Lunar SAR Rules & Info: https://github.com/SimDynamX/SpaceTeamsPro/discussions/33

Link to Starter Code repository: https://github.com/SimDynamX/STU_Lunar_SAR

Subsection Links:

How to set up your development environment

  1. Do the setup steps from here: STU Lunar SAR Competitor Setup & Learning

There are several ways to do python development; here is one way that is the typical way we at SimDynamX set it up.

  1. Install VS Code and open it
  2. Do Open Folder to C:\Users\YOUR WINDOWS USERNAME\AppData\Local\SimDynamX\STPro\Assets
  3. Open one of the .py files in Repos/STU_Lunar_SAR_YourTeamName
  4. If prompted, install the recommended python extensions, and wait for those installs to complete
  5. Set your Python interpreter to the one that was installed during setup
    1. While having a python file open, click the python version number in the bottom right of the screen image
    2. At the top of the screen, click Enter interpreter path... image
    3. Click Find...
    4. Navigate to AppData\Local\SimDynamX\STPro\Versions\(the highest version number in there)\py\Scripts and select python.exe, then Select Interpreter image
    5. Should now be switched to the correct python interpreter image
    6. And you should get syntax highlighting for the spaceteams python module. image

Which files to modify for Task 1

The files in the starter code repository that are in the top folder (not API or sims subfolders) are available to be edited by competitors. There are some exceptions:

  • There are a few things you will need to edit in the sims/STU2_SAR_Task1.json sim config file:
    • All paths that reference STU_Competition2, like the Source file for the MissionManager system will need to have the STU_Competition2 changed out with the repo/folder name that you are using as the name for your copy of the starter code repo. image
  • Don't edit CompetitionBackend.py
  • You can in general change the Source on any of these python systems in order to change out which .py file in your repo they are using:
    • MissionManager
    • EntityBehavior_(any)

Where you should start is making small modifications to the starter code in ExampleMM_TaskgraphSimple.py, and seeing what happens when running the sim. Make good use of st.OnScreenLogMessage() and st.OnScreenAlert() for easy-to-see printing.

Which files to modify for Task 2

Same as Task 1. There are more EntityBehavior_ Systems to modify the Source on than before.

Which files to modify for Final Task

Same as Task 2.

How to run the Task sims in SpaceCRAFT (the graphics portion of SpaceTeamsPRO)

  • Do the path rename change mentioned above (after having done all the setup steps)
  • Open SpaceCRAFT.exe
  • Log in
  • On the main menu, Sim->User-Created Sims->Search for and click Repos/STU_Lunar_Sar_YourTeamName/sims/STU2_SAR_Task1.json->Confirm

Handling errors and crashes

If the Compute_Server crashed on sim startup, the sim may seem to start normally but around 10 seconds later a dialog box with this message comes up:

UE Server: Failed to connect to server, reason: [10061] Failed to connect to locally-running Compute_Server for simulation; this could mean Compute_Server crashed. 
Check the latest-modified file in the Compute_Server/Logs folder for more details. 
Would you like to return to the main menu? "No" quits the application.

If the Compute_Server crashed during the sim, a dialog box with this message comes up:

Simulation stopped; reason: [10054] Existing Platform data connection to locally-running Compute_Server was forcibly closed; this could mean Compute_Server crashed. 
Check the latest-modified file in the Compute_Server/Logs folder for more details. 
Would you like to return to the main menu? "No" quits the application.

If something like this happens, find out what caused the sim to crash by finding the set of most-recently-edited .log files in /Compute_Server/Logs. image

The shortest-name log (maybe Sim_STU2_SAR_Task1.log) will be the main Compute_Server log. Open that one.

Common errors:

  • Python System Instance MissionManager invalid Source filepath
    • Maybe you didn't rename the folder as explained above?
    • Maybe you renamed the .py file but didn't update the sim config to match?
  • [FATAL]---[Main->PySys_MissionManager @NID:2]: ST Netcode: [10054] Existing connection was forcibly closed by the remote host. This is an unhandled disconnect, probably due to Python System MissionManager crashing / terminating. See Compute_Server/Logs/Sim_STU2_SAR_Task1_PySys_MissionManager.log for more details. Terminating program due to this unhandled disconnect.
    • Do as it says; the error is likely in that Python System's log file.

API Documentation

Coordinate Bounds

The crash site location is guaranteed to be within these bounds. They are given in both latitude/longitude pairs and local XY coordinates.

Latitude bounds: [-89.39°, -89.34°] = [89.39° S, 89.34° S]

Longitude bounds: [-139.0°, -134.5°] = [139.0° W, 134.5° W]

XY coordinates (meters):

(-317.154, 735.680)  - - - - - - - - - - - - - - - (1199.211, 795.979)
         |                                                  |
         |                                                  |
         |                                                  |
         |                    y                             |
         |                    ↑                             |
         |                     → x, North                   |
         |                                                  |
         |                                                  |
         |                                                  |
         |                                                  |
(-316.460, -718.026) - - - - - - - - - - - - - - - (1199.962, -776.878)

Available Mission Manager Functions

This section lists the commands available for use in the mission manager script. Each command is designed to control specific actions of an entity within the simulation environment.

Commanding (In General)

  • SendCommand(en, command_type, command)

    • NOTE: Call this directly when you are not using a taskgraph to dispatch this command.
    • Sends a command to an entity.
    • The payload can be used to send additional information about the command.
    • Returns whether command sent successfully; will need to call this function again if the subject entity is not in comms.
    • Use EnHasComms(en) to check if entity has comms.
    • Parameters:
      • en: The entity to command (e.g., LTV1).
      • command_type: The command type (e.g., MoveToCoord).
      • command: Command object constructed with a valid Command_[command name] function (listed in the next section).
    • Example Usage:
      # Send a camera pan command
      azimuth = 150.0 #deg
      elevation = 10.0 #deg
      mm.SendCommand(LTV1, "CameraPan", Command_CameraPan(LTV1, azimuth, elevation, ""))
      
  • OnCommandComplete(en, command_type, reaction)

    • Registers a reaction (Python function with an st.ParamMap argument) to a command completion.
      • A lambda function can also be used as a reaction if additional inputs are needed.
    • Parameters:
      • en: The entity to register a reaction for (e.g., LTV1).
      • command_type: The command type (e.g., MoveToCoord).
      • reaction: Python function object (type the name only unless using a lambda).
    • Example Usage (no lambda):
      # Reaction to a stop command
      def Stop_Complete_LTV1(payload : st.ParamMap):
          # Example of a reaction (printing something)
          st.OnScreenLogMessage("LTV1: Stop command complete.", "MM Surface Movement", st.Severity.Info)
          # If this command is part of a taskgraph, also run the following to mark this task as completed.
          LTV1_TaskComplete(payload)
      # Registering the reaction
      mm.OnCommandComplete(LTV1, "Stop", Stop_Complete_LTV1)
      
    • Example Usage (with lambda):
      # Reaction to a stop command
      def Stop_Complete(payload : st.ParamMap, en : st.Entity):
          # Example of a reaction (printing something)
          st.OnScreenLogMessage(f"{en.getName()}: Stop command complete.", "MM Surface Movement", st.Severity.Info)
          # If this command is part of a taskgraph, also run the following to mark this task as completed.
          General_TaskComplete(payload, en)
      # Registering the reaction
      mm.OnCommandComplete(LTV1, "Stop", lambda payload : Stop_Complete(payload, LTV1))
      
  • OnCommandFail(en, command_type, reaction)

    • Registers a reaction (Python function with an st.ParamMap argument) to a command failure.
      • A lambda function can also be used as a reaction if additional inputs are needed.
    • Parameters:
      • en: The entity to register a reaction for (e.g., LTV1).
      • command_type: The command type (e.g., MoveToCoord).
      • reaction: Python function object (type the name only unless using a lambda).
    • Example Usage (no lambda):
      # Reaction to a stop command
      def Stop_Fail_LTV1(payload : st.ParamMap):
          # Example of a reaction (printing something)
          st.OnScreenLogMessage("LTV1: Stop command complete.", "MM Surface Movement", st.Severity.Info)
          # If this command is part of a taskgraph, also run the following to mark this task as completed.
          LTV1_TaskComplete(payload)
      # Registering the reaction
      mm.OnCommandFail(LTV1, "Stop", Stop_Fail_LTV1)
      
    • Example Usage (with lambda):
      # Reaction to a stop command
      def Stop_Fail(payload : st.ParamMap, en : st.Entity):
          # Example of a reaction (printing something)
          st.OnScreenLogMessage(f"{en.getName()}: Stop command failed.", "MM Surface Movement", st.Severity.Error)
          # If this command is part of a taskgraph, also run the following to mark this task as completed.
          General_TaskFail(payload, en)
      # Registering the reaction
      mm.OnCommandFail(LTV1, "Stop", lambda payload : Stop_Fail(payload, LTV1))
      

Checking for Communications Line of Sight

  • EnHasComms(en)
    • Checks if an entity has line of sight to any communications antenna.
    • Parameters:
      • en: The entity to check (e.g., LTV1).
    • Example Usage:
      # Check for LTV1 comms
      has_comms = mm.EnHasComms(LTV1)
      
    • An equivalent function called HasComms is provided in EntityTelemetry

Movement Commands

  • Command_MoveToCoord(en, xy, task_id)

    • Moves an entity to a specified XY coordinate.
    • Parameters:
      • en: The entity to be moved (e.g., LTV1).
      • xy: An XY object representing the target coordinate.
      • task_id: A unique string identifier for this move command.
    • Example Usage:
      # Create an XY coordinate object
      target_xy = XY(100.0, 50.0)
      
      # Create a MoveToCoord command for LTV1 with task ID "Move1"
      move_command = Command_MoveToCoord(LTV1, target_xy, "Move1")
      
  • Command_Stop(en, task_id)

    • Stops an entity that is currently moving. Has no effect if the entity is not moving; always completes (doesn't fail) if the entity has comms.
    • Parameters:
      • en: The entity to be stopped (e.g., LTV1).
      • task_id: A unique string identifier for this stop command.
    • Example Usage:
      # Create a Stop command for LTV1 with task ID "Stop1"
      stop_command = Command_Stop(LTV1, "Stop1")
      

Orientation Commands

  • Command_RotateToAzimuth(en, azimuth, task_id)
    • Rotates an entity to a specified azimuth angle.
    • Parameters:
      • en: The entity to be rotated.
      • azimuth: The target azimuth angle in degrees.
      • task_id: A unique string identifier for this rotate command.
    • Example Usage:
      # Create a RotateToAzimuth command for LTV2 to face 90 degrees with task ID "Rotate1"
      rotate_command = Command_RotateToAzimuth(LTV2, 90.0, "Rotate1")
      

Camera Commands

  • Command_CameraPan(en, azimuth_deg, elevation_deg, task_id)

    • Controls the pan of a camera attached to an entity.
    • Parameters:
      • en: The entity whose camera will be panned.
      • azimuth_deg: The target azimuth angle in degrees.
      • elevation_deg: The target elevation angle in degrees.
      • task_id: A unique string identifier for this camera pan command.
    • Example Usage:
      # Create a CameraPan command for LTV1 with task ID "CameraPan1"
      camera_command = Command_CameraPan(LTV1, 45.0, 30.0, "CameraPan1")
      
  • Command_CaptureImage(en, exposure, task_id)

    • Captures an image using a camera attached to an entity.
    • Parameters:
      • en: The entity whose camera will capture the image.
      • exposure: The exposure value for the image capture.
      • task_id: A unique string identifier for this image capture command.
    • Example Usage:
      # Create a CaptureImage command for LTV2 with task ID "Capture1"
      capture_command = Command_CaptureImage(LTV2, 4.0, "Capture1")
      

Antenna Commands

  • Command_PickUpAntenna(en, task_id)

    • Commands an entity to pick up an antenna.
    • Parameters:
      • en: The entity that will pick up the antenna.
      • task_id: A unique string identifier for this command.
    • Example Usage:
      # Create a PickUpAntenna command for LTV1 with task ID "PickUp1"
      pick_up_command = Command_PickUpAntenna(LTV1, "PickUp1")
      
  • Command_PlaceDownAntenna(en, task_id)

    • Commands an entity to place down an antenna.
    • Parameters:
      • en: The entity that will place down the antenna.
      • task_id: A unique string identifier for this command.
    • Example Usage:
      # Create a PlaceDownAntenna command for LTV1 with task ID "PlaceDown1"
      place_down_command = Command_PlaceDownAntenna(LTV1, "PlaceDown1")
      

Image Processing

This section describes how to process images captured by an entity's camera.

The example code below shows the ProcessImage function, which converts the raw image data to an RGB image for display or further analysis.

# Camera image processing
img_rgb = None
image_changed = False

def ProcessImage(image : st.CapturedImage):
    global img_rgb
    global image_changed
    resx = image.properties.ResolutionX
    resy = image.properties.ResolutionY
    img_r = np.array(image.PixelsR, dtype=np.uint8).reshape((resy, resx))
    img_g = np.array(image.PixelsG, dtype=np.uint8).reshape((resy, resx))
    img_b = np.array(image.PixelsB, dtype=np.uint8).reshape((resy, resx))

    # Stack channels into a single 3D array (height x width x 3)
    img_rgb = np.stack((img_r, img_g, img_b), axis=-1)
    image_changed = True

# Example Usage:
# Assuming 'payload' is a st.ParamMap object received from a CameraCapture command completion
image = st.CapturedImage()
image.PixelsR = payload.GetParamArray(st.VarType.uint8, "PixelsR")
image.PixelsG = payload.GetParamArray(st.VarType.uint8, "PixelsG")
image.PixelsB = payload.GetParamArray(st.VarType.uint8, "PixelsB")
image.properties.ResolutionX = payload.GetParam(st.VarType.int32, "ResolutionX")
image.properties.ResolutionY = payload.GetParam(st.VarType.int32, "ResolutionY")
image.properties.EV = payload.GetParam(st.VarType.double, "Exposure")
image.properties.FOV = payload.GetParam(st.VarType.double, "FOV")
ProcessImage(image)

Entity Telemetry

The EntityTelemetry module provides functions to access telemetry data for entities in the simulation. This data includes position, orientation, sensor readings, and communication status.

Example Usage:

import API.EntityTelemetry as ET

# Get the current XY position of LTV1
current_xy, has_comms = ET.GetCurrentXY(LTV1)

# Get lidar obstacle data from LTV1
rel_vecs, radii, had_comms = ET.GetLidarObstacles(LTV1)
st.logger_info("Lidar info: " + str(rel_vecs) + ", " + str(radii) + ", " + str(had_comms))

Entity Telemetry Functions

  • LatLonAltToXY(lla)

    • Converts an st.PlanetUtils.LatLonAlt object (consisting of latitude, longitude, and altitude) to local XY coordinates.
    • Returns: an XY object with properties XY.x and XY.y.
  • XYToLatLonAlt(xy)

    • Converts an XY object (consisting of local x and y location) to an st.PlanetUtils.LatLonAlt object (consisting of latitude, longitude, and altitude).
    • Returns: an st.PlanetUtils.LatLonAlt object (lla) with member getter functions lla.lat(), lla.lon(), and lla.alt().
  • HasComms(en)

    • Checks if an entity has communication capabilities.
    • Returns: True if the entity has comms, False otherwise.
  • GetMovementState(en)

    • Retrieves the movement state of an entity.
    • Returns: A tuple containing:
      • The movement state (e.g., "Moving", "Stopped").
      • A boolean indicating comms status.
    • Docstring:
      '''
      Get the movement state of the entity.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      
      Movement states:
      Loiter      - The entity is stationary.
      LoiterUntil - (unused) The entity is stationary until a certain time.
      Ride        - (unused) The entity is embarked on another entity.
      StopRide    - (unused) The Entity is disembarking from another entity.
      Startup     - (unused) The entity is starting up.
      Moving      - The entity is moving to a destination.
      Reversing   - The entity is moving in reverse to a destination.
      Cooldown    - (unused) The entity is shutting down.
      '''
      
  • IsMoving(en)

    • Checks if an entity is currently in motion.
    • Returns: A tuple containing:
      • A boolean indicating if the entity is moving.
      • A boolean indicating comms status.
    • Docstring:
      '''
      Get whether the entity is currently moving.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      '''
      
  • GetCurrentXY(en)

    • Gets the current XY coordinates of an entity.
    • Returns: A tuple containing:
      • An XY object representing the coordinates.
      • A boolean indicating comms status.
    • Docstring:
      '''
      Get the current XY coordinate of the entity.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      '''
      
  • GetMoveToXY(en)

    • Gets the target XY coordinates to which an entity is moving.
    • Returns: A tuple containing:
      • An XY object representing the target coordinates.
      • A boolean indicating comms status.
    • Docstring:
      '''
      Get the current move-to waypoint XY coordinate of the entity.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      '''
      
  • GetAzimuth(en)

    • Gets the current azimuth angle of an entity.
    • Returns: A tuple containing:
      • The azimuth angle in degrees.
      • A boolean indicating comms status.
    • Docstring:
      '''
      Get the current azimuth of the entity.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      '''
      
  • GetLidarObstacles(en)

    • Retrieves lidar sensor data about obstacles around an entity.
    • Returns: A tuple containing:
      • A list of relative vectors to obstacles.
      • A list of their radii.
      • A boolean indicating comms status.
    • Docstring:
      '''
      Returns a list of relative vectors to obstacles and their radii.
      3rd element of the returned tuple is whether the entity has comms and thus whether the
      returned values are valid.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      '''
      
  • GetTargetScanStatus(en)

    • Checks if the target has been detected by the scanner.
    • Returns: A tuple containing:
      • A boolean indicating target detection.
      • The target's location as an XY object.
      • A boolean indicating comms status.
    • Docstring:
      '''
      Returns whether a target has been found and the location of the found target.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      '''
      
  • GetStateOfCharge(en)

    • Returns the 0-1 fraction of charge of the entity's battery.
    • Returns: A tuple containing:
      • The state of charge.
      • A boolean indicating comms status.
    • Docstring:
      '''
      Returns the 0-1 fraction of charge of the entity's battery.
      Also returns whether the entity had comms.
      If the entity does not have comms, the function will return a default value.
      '''
      
  • GetChargingStationXY()

    • Returns the XY coordinate of the charging station.
    • Returns: An XY object representing the charging station coordinates.
    • Docstring:
      '''
      Returns the XY coordinate of the charging station.
      Doesn't require comms.
      '''
      
  • GetAntennaXY(index)

    • Returns the XY coordinate of the appropriate antenna.
    • Returns: An XY object representing the antenna coordinates.
    • Docstring:
      '''
      Returns the XY coordinate of the appropriate antenna.
      Index is 1->3.
      Doesn't require comms.
      '''
      

Slope vs. Power Curves

Each entity utilizes a variable amount of power based on the slope angle it sees. Please reference the plots below for each entity:

LTV

LTV Slope Power Curve

Scout

Scout Slope Power Curve

Sampling Rover

Sampling Slope Power Curve

Truck Rover

Truck Slope Power Curve

Excavator Rover

Excavator Slope Power Curve

Slope vs. Speed Curves

Each entity's speed is limited based on the slope angle it encounters. Please reference the plots below for each entity:

LTV

ltv_speed

Scout

scout_speed

Sampling Rover

sampler_speed

Truck Rover

truck_speed

Excavator Rover

excavator_speed

Task Graph

The TaskGraph module provides a way to define and manage a sequence of tasks with dependencies. Each task represents an action to be performed by an entity, such as moving to a location or capturing an image. The task graph ensures that tasks are executed in the correct order and that dependencies between tasks are met.

Example Usage:

import TaskGraph as TG

# Create a task graph
task_graph = TG.TaskGraph()

# Define tasks
task1 = TG.Task("Task1", Command_MoveToCoord(LTV1, XY(10, 20), "Task1"))
task2 = TG.Task("Task2", Command_RotateToAzimuth(LTV1, 90, "Task2"))

# Add tasks to the graph with dependencies
task_graph.add_task(task1, [])  # No dependencies for the first task
task_graph.add_task(task2, ["Task1"])  # task2 depends on task1

# ... (rest of the script)