Sensors

This section explains how to use (and optionally extend) sensor models in PyRoboSim.

Sensor Definitions

The pyrobosim/sensors module contains all sensor model implementations.

What to Implement in a Sensor

The sensors implemented in PyRoboSim provide general functionality to simulate a sensor, as well as data for visualization in the UI.

First, you want subclass from the Sensor class and create a constructor as follows.

from pyrobosim.sensors.types import Sensor

class MyNewSensor(Sensor):

    plugin_name = "my_sensor"  # Needed to register the plugin

    def __init__(
        self,
        *,
        update_rate_s: float,
        initial_value: float,
    ) -> None:
        super().__init__()
        self.update_rate_s = update_rate_s
        self.latest_value = initial_value

Next, you should implement update() and get_measurement() functions. These update the internals of the sensor and return the latest measurement, respectively.

def update(self) -> None:
    # Increments the sensor value by 1.
    self.latest_value += 1.0

def get_measurement(self) -> float:
    return self.latest_value

If you want to run the sensor automatically in the background, you can also implement a thread_function() function. This will only take effect if your robot is created with the start_sensor_threads argument set to True.

def thread_function(self) -> None:
    if self.robot is None:  # This is created in the constructor!
        return

    # The `is_active` attribute should be used to cleanly
    # stop this thread when the sensor is shut down.
    while self.is_active:
        t_start = time.time()
        self.update()
        t_end = time.time()
        time.sleep(max(0.0, self.update_rate_s - (t_end - t_start)))

For visualization, you can provide setup_artists() and update_artists() methods.

from matplotlib.artist import Artist
from matplotlib.patches import Circle
from matplotlib.transforms import Affine2D

    def setup_artists(self) -> list[Artist]:
        """Executes when the sensor is first visualized."""
        pose = self.robot.get_pose()
        self.circle = Circle(
            (pose.x, pose.y),
            radius=1.0,
            color="r",
        )
        return [self.circle]

    def update_artists(self) -> None:
        """Updates the artist as needed."""
        pose = self.robot.get_pose()
        new_tform = Affine2D().translate(pose.x, pose.y)
        self.circle.set_transform(new_tform)

To serialize to file, which is needed to reset the world, you should also implement the to_dict() method. Note the plugin_name attribute, which contains the name of the sensor you defined earlier on.

def to_dict(self) -> dict[str, Any]:
    return {
        "type": self.plugin_name,
        "update_rate_s": self.update_rate_s,
        "initial_value": self.initial_value,
    }

At this point, you can import your own sensor in code and load it dynamically using the Sensor parent class.

from pyrobosim.sensors import Sensor
from my_module import MyNewSensor  # Still need to import this!

sensor_class = Sensor.registered_plugins["my_sensor"]
sensor = sensor_class(update_rate_s=0.1, initial_value=42)

… or from YAML world files.

robots:
  name: robot
  sensors:
    my_cool_sensor:
      type: my_sensor
      update_rate_s: 0.1
      initial_value: 42

If you would like to implement your own sensor, it is highly recommended to look at the existing sensor implementations as a reference. You can also always ask the maintainers through a Git issue!