| name | add-new-sensor |
| description | Add a new sensor device to CLOiSim, including the Device subclass, SDF pipeline wiring, and transport plugin. Use when: adding a new sensor type, implementing a new SDF sensor, creating a new device class. |
Add a New Sensor Device
End-to-end procedure for adding a new sensor type to CLOiSim. This requires coordinated changes across 4 layers: Device, Implement, Import, and Plugin.
When to Use
- Adding a new SDF-defined sensor type (e.g., force/torque, magnetometer, altimeter)
- Porting a sensor from another simulator or from ROS sensor_msgs
- The SDFormat package already supports the sensor type, or you will add support
Procedure
1. Create the Device Class
Create Assets/Scripts/Devices/MyNewSensor.cs:
using UnityEngine;
using messages = cloisim.msgs;
namespace SensorDevices
{
public partial class MyNewSensor : Device
{
private messages.MyMessage _msg;
private Noise _noiseX;
protected override void OnAwake()
{
Mode = ModeType.TX_THREAD;
DeviceName = name;
}
protected override void OnStart()
{
}
protected override void OnReset()
{
}
protected override void InitializeMessages()
{
_msg = new messages.MyMessage();
_msg.Stamp = new messages.Time();
}
protected override void SetupMessages()
{
_msg.EntityName = DeviceName;
}
protected override void GenerateMessage()
{
_msg.Stamp.Set(GetNextSyntheticTime());
PushDeviceMessage<messages.MyMessage>(_msg);
}
private void FixedUpdate()
{
}
}
}
Key decisions:
- Timer-polled (IMU, GPS): override
GenerateMessage() to build + push messages directly. TX thread calls it at UpdateRate.
- Event-driven (camera, lidar): call
EnqueueMessage() from async callbacks (e.g., AsyncGPUReadback). Default GenerateMessage() drains the queue.
- Physics-based: use
FixedUpdate() for transform/velocity sampling. Non-physics: use Update().
2. Add Noise Support (Optional)
If the sensor needs noise, add setup methods:
public void SetupNoise(in SDFormat.Noise noise)
{
_noiseX = new Noise(noise);
}
public void SetupNoises(in SDFormat.XxxSensor sensor)
{
if (sensor.XNoise != null)
_noiseX = new Noise(sensor.XNoise);
}
Apply noise in FixedUpdate() or GenerateMessage():
_noiseX.Apply<float>(ref value, Time.fixedDeltaTime);
_noise.Apply<double>(dataArray);
3. Add the Implement Extension Method
Edit Assets/Scripts/Tools/SDF/Implement/Implement.Sensor.cs — add a new static extension method:
public static Device AddMyNewSensor(this GameObject targetObject, in SDFormat.MyNewSensorType element)
{
var newSensorObject = new GameObject();
targetObject.AttachSensor(newSensorObject);
var sensor = newSensorObject.AddComponent<SensorDevices.MyNewSensor>();
sensor.DeviceName = newSensorObject.GetFrameName();
if (element.Noise != null)
sensor.SetupNoise(element.Noise);
return sensor;
}
4. Add the Import Case
Edit Assets/Scripts/Tools/SDF/Import/Import.Sensor.cs — add a case in the sensor type switch:
case "my_new_sensor":
var myData = sensor.GetMyNewSensorData();
device = targetObject.AddMyNewSensor(myData);
break;
The import layer automatically handles after the switch:
- Setting
UpdateRate from SDF
- Setting
EnableVisualize
- Applying sensor pose offset
- Tagging the GameObject as
"Sensor"
5. Create the Plugin
Create Assets/Scripts/CLOiSimPlugins/MyNewSensorPlugin.cs:
using System.Collections;
using UnityEngine;
public class MyNewSensorPlugin : CLOiSimPlugin
{
private SensorDevices.MyNewSensor _sensor;
protected override void OnAwake()
{
_type = ICLOiSimPlugin.Type.SENSOR;
_sensor = gameObject.GetComponent<SensorDevices.MyNewSensor>();
}
protected override IEnumerator OnStart()
{
if (RegisterServiceDevice(out var portService, "Info"))
{
AddThread(portService, ServiceThread);
}
if (RegisterTxDevice(out var portTx, "Data"))
{
AddThread(portTx, SenderThread, _sensor);
}
yield return null;
}
protected override void HandleCustomRequestMessage(
in string requestType, in Any requestValue, ref DeviceMessage response)
{
switch (requestType)
{
case "request_transform":
var devicePose = _sensor.GetPose();
var deviceName = _sensor.DeviceName;
SetTransformInfoResponse(ref response, deviceName, devicePose, _parentLinkName);
break;
default:
break;
}
}
}
6. Verify the SDF Package
Ensure the com.lge-ros2.sdformat package supports the sensor type. If not:
- Add the domain class to the SDFormat package
- Add the sensor type string mapping
- Update the package version
7. Verify SDF Plugin Resolution
The SDF <plugin filename="MyNewSensorPlugin"> attribute must match the C# class name exactly, since the importer uses Type.GetType(pluginLibraryName).
Checklist