Add a Sensor#
This page gives the procedure to add a sensor to the game, from the model to the ROS 2 topic. It also gives the rules for threads and noise that keep a recording deterministic.
Before You Start#
- Build the game and the ROS 2 workspace. Refer to Build the Game and Build the ROS 2 Workspace.
- Read the page of a sensor that is almost the same. INS and GNSS shows a sensor of the physics thread.
- Read Camera or LiDAR for a sensor that needs the renderer or the scene.
- Read the frame format on the Sensor Stream page.
- Read the Coding and Naming Conventions.
The examples on this page add a barometric altimeter with the name baro. The game does not contain this sensor.
The Path of a Sample#
A sample goes through these stages. Each stage has one section on this page.
| Stage | File | Item to Add |
|---|---|---|
| Model | Acres/Source/Acres/Acres<Name>Model.h |
The mathematics, without Unreal types. |
| Configuration | AcresSensors.h, AcresSensors.cpp, sensors.json |
The keys, the options and the range checks. |
| Menu | AcresSession.cpp |
The rows of the Sensors tab. |
| Schedule | AcresSensors.cpp |
The stream, the generator and the call in PhysicsSample or GameTick. |
| Files | AcresSensors.cpp |
The row of the .jsonl file, the binary files and the keys of episode.json. |
| Sensor stream | AcresSensorStream.h, AcresSensors.cpp |
The frame type, the payload and the block of the hello message. |
| ROS 2 | ROS/acres_sim |
The frame constant, the conversion function, the publisher and the transform. |
| ACRES Core | ROS/acres_core_sim |
The same model behind the same sockets, when ACRES Core needs the sensor. |
| Tests and documentation | Tools, ROS/acres_sim/test, Documentation |
Unit tests, a recording check and the model page. |
Rules#
Obey these rules in all stages.
| Topic | Rule |
|---|---|
| Clock | The physics step of 1/120 s is the clock. A sample time is a count of steps divided by 120. |
| Physics thread | PhysicsSample runs on the physics thread. Read only FAcresSensorPhysicsSample there. Do not read the Unreal world. |
| Game thread | Scene queries and renders run in GameTick. Record the pose and the time of the sample on the physics thread first. |
| Thread pool | Encode large files on the thread pool. Count the tasks in EncodesInFlight. |
| Queues | Give each queue a limit. Count each dropped sample and report the count in episode.json. |
| Noise | Give the sensor its own FAcresPcg32 stream. Use the same number of values for each sample. |
| Noise off | -SensorNoNoise sets the standard deviations to zero. The sensor must continue to use its values. |
| Units and frames | Use SI units. Output world quantities in ENU and sensor quantities in body FLU. |
| Mounts | Give a mount as FLU metres from the chassis origin and as roll, pitch and yaw in degrees. |
| Bad values | Stop the game with checkf when a key is out of its range. |
Write the Model#
Put the mathematics in a file without Unreal types when ACRES Core or a test program must use it.
The files AcresGeoUtm.h and AcresLidarPhysics.h are examples: plain C++ in the headers, no CoreMinimal.h.
Give a model with a .cpp file a name that ends in Model.cpp, as the other models have.
- Create
Acres/Source/Acres/AcresBaroModel.h. - Write a
/// @filecomment that describes the model and gives its publications. - Write the functions with SI units in the argument names.
-
Write a
///comment withArgs:andReturns:for each function.#pragma once #include <cmath> /// @file /// Barometric altimeter (example): pressure of the ISA troposphere at a height, and the altitude that a sensor /// computes from a pressure. No Unreal types. namespace AcresBaro { /// Static pressure of the standard atmosphere. /// /// Args: /// AltitudeM: Height above mean sea level, m. /// Returns: The pressure, Pa. inline double PressurePa(double AltitudeM) { return 101325. * std::pow(1 - 2.25577e-5 * AltitudeM, 5.25588); } } // namespace AcresBaro -
Add the
.cppfile toACRES_MODEL_SOURCESinCore/CMakeLists.txtwhen ACRES Core uses the model.
Note
The receiver model AcresGnssModel.cpp and the camera and LiDAR models use Unreal types. ACRES Core does not compile them.
Add the Configuration Keys#
The structure FAcresSensorConfig holds all settings. The function LoadSensorConfig reads them.
-
Add the fields to
FAcresSensorConfiginAcresSensors.h: a switch, a rate, a mount and the noise. -
Read the block in
LoadSensorConfig. UseTryGetObjectFieldso that a file without the block continues to load. -
Add the mount to the loop that moves the mounts of the frame
base_footprintto the chassis frame. - Add the options below the other
FParsecalls:-SensorBaroHz=and-SensorNoBaro. - Set the noise to zero in the block of
-SensorNoNoise. - Add the rate to the check of 1 Hz to 120 Hz. Add the mount to the check of 10 m.
- Add the keys to
FAcresSensorConfig::ToJson. The filessensors-config.jsonandepisode.jsonthen contain them. - Add the block to
Acres/Content/Simulation/sensors.jsonwith the defaults and aprovenancetext. - Add the values of the Polaris to
sensors_polaris.jsonwhen they are different.
A session folder contains a copy of sensors.json. Thus an old session has no block for the new sensor.
The block ins is the example of a block that is not mandatory.
Add the mount and the settings to DataSignature only when they change the camera or LiDAR data.
Add the Menu Rows#
The Sensors tab of the menu is a table in AcresSession.cpp. One row is one setting.
-
Add one row for the switch and one row for each setting that a user changes frequently.
-
Add the switch to the test
AnySensorinFAcresSession::Prepare.
The identifier of a row is sen: and the JSON path of the key. The menu writes the value into the sensors.json of the session folder.
The page Menu Settings comes from this table. The build makes it again.
Schedule the Sensor in the Recorder#
The class FAcresSensorRecorder owns the streams, the generators and the schedule.
- Add the stream: increase
StreamCount, add the name toStreamNamesand add a value toEStream. - Add a generator member, for example
RngBaro. -
Seed the generator in
Startwith the next free stream number. -
Add the state of the sensor to the resets in
Startand to the block forS.ResetEpoch != LastEpoch. - Add the fields that the sensor reads to
FAcresSensorPhysicsSample. - Fill the new fields in
AAcresVehiclePawn::AsyncPhysicsTickActor, before the call ofPhysicsSample. -
Call the model in
PhysicsSamplewhen the sensor is due.if (Config.Baro && Due(BaroStream, Config.BaroHz, TimeS)) { const FVector MountM = P + Q.RotateVector(BodyFromFlu(Config.BaroFlu)); const double AltitudeM = OriginFt.Z * AcresGeo::UsSurveyFootM + MountM.Z; const double PressurePa = AcresBaro::PressurePa(AltitudeM) + Config.BaroStdPa * RngBaro.Gaussian(); Emit(BaroStream, FString::Printf(TEXT("{\"physics_step\":%llu,\"pressure_pa\":%.3f"), S.Step, PressurePa), TimeS); }
The stream numbers 0 to 5 are in use. The table gives them.
| Stream | Seed | Sequence |
|---|---|---|
| GNSS errors | seed |
1 |
| IMU | seed + 1009 |
2 |
| LiDAR | seed + 2018 |
3 |
| GNSS constellation | seed + 3027 |
4 |
| LiDAR weather | seed + 4036 |
5 |
| INS | seed + 5045 |
6 |
Due gives the first sample at \(t = 0\) and then one sample each \(1/f\) seconds.
A sensor of the game thread uses a different pattern. The LiDAR is the example.
- On the physics thread, put a request with the pose and the time into a queue. Use the queue mode
Spsc. - In
GameTick, take the request and do the scene query or the render. - Encode the result on the thread pool.
- Return
falsefromIsIdlewhile a request or a task is in work.
In lockstep, the simulator control channel waits for IsIdle before it answers a step request.
A sensor that does not report its work in IsIdle thus sends its data after the barrier.
Write the Rows and the Files#
Start opens one file <stream>.jsonl for each name in StreamNames. One thread writes all rows.
| Function | Use |
|---|---|
Emit(Stream, Row, SampleS) |
A row with the modelled latency. Give the row without the closing brace. The function adds the three time keys. |
Write(Stream, Line) |
A complete row without latency, for example a truth row. |
AcresSensorIo::WriteBytes(Path, Data, Bytes) |
A binary file, for example a point cloud. Create its folder in Start. |
- Write each row as one JSON object with the unit in each key, for example
pressure_pa. - Add
physics_stepto each row. A reader then joins the row withtruth.jsonl. - Add the count of the stream to the log line
ACRES_SENSOR_STOPinFinishand toStatusLabel. - Add the mount to
extrinsics_flu_minWriteManifest. - Add a block with the description of the model to
episode.jsoninWriteManifest. - Add one sentence to the array
limitationsinWriteManifest.
The object counts of episode.json gets the new stream automatically. The loop uses StreamNames.
The Session Log page gives the files of an episode.
Add the Frame to the Sensor Stream#
The sensor stream sends the data to the ROS 2 bridge while the simulation runs.
- Add a value to
FAcresSensorStream::EFrameinAcresSensorStream.h. The next free value is 7. - Describe the payload in the file comment of
AcresSensorStream.h: each field with its type and unit. -
Send the frame from the recorder at the same place as the row.
-
Add a block to
StreamHellowith the switch, the rate, the frame name and the mount frombase_footprint. - For a large frame, add the type to the test
bHeavyinFAcresSensorStream::Publish.
The stamp of a frame is the physics time of the vehicle. Publish adds the offset to the world time.
The counters of the stream have 8 entries, for the frame types 0 to 7. A frame type above 7 needs larger arrays.
The stream drops large frames when more than 96 MB wait in its queue.
An old ROS 2 bridge counts a frame of a type that it does not know and does not fail.
CAUTION
Do not change the sequence of the values in an existing payload. A bridge of a different version then reads incorrect values.
Convert the Frame in the ROS 2 Bridge#
The package acres_sim reads the frames and publishes the messages.
- Add the frame constant to
FrameTypeinROS/acres_sim/include/acres_sim/stream.hpp, with a comment for the payload. -
Declare a conversion function in
conversions.hppand write it insrc/conversions.cpp. The function must not use a node. -
Create the publisher in
VehicleBridge::setupinsrc/vehicle_bridge.cpp. Usevehicle_qos(). - Add the frame type to
VehicleBridge::on_frameand publish the message there. - Send a large frame through the queue of the heavy frames, as the LiDAR and the camera do.
- Add the static transform of the mount to
mount_transforms. Read the mount from the hello message. - Use a standard message type when one exists. For a new type, refer to Add a ROS 2 Interface.
- Add the frame to
ROS/acres_sim/test/fake_game.py. The integration test then gets the new topic.
In lockstep, the bridge must publish a frame that goes through a queue before the barrier of its step. The queue of the heavy frames carries the barrier for this reason. The Topics page and the Sensor Stream page must list the new topic and frame.
Add the Sensor to ACRES Core#
Do this stage only when a client of ACRES Core needs the sensor. FInsModel in ROS/acres_core_sim is the example.
- Use the model file of the first stage. Do not copy the equations.
- Read the same keys from
sensors.jsonand from the overlay, asFInsConfig::Loaddoes. - Use a generator with the same algorithm and the same seed, as
FPcg32does. - Call the model in
FCoreSimServer::EmitAfterStepand send the frame withEncodeFrame. - Add the block of the sensor to the hello message of the server.
- Compute the sample also when the server has no client. The noise then does not depend on the client.
Write the Tests#
| Test | Location | Pattern |
|---|---|---|
| Model mathematics | Tools/<Name>Model/ with a build.sh |
Tools/LidarModel, Tools/PolarisModel/polaris_tests.cpp |
| Conversion to messages | ROS/acres_sim/test/test_conversions.cpp |
Conversions.InsToPolarisMessages |
| Frame layout | ROS/acres_sim/test/test_stream.cpp |
Stream.ReadsEveryFrameTypeFromASocket |
| Bridge with a simulated game | ROS/acres_sim/test/test_integration.py, fake_game.py |
test_maxxum_guidance_can_and_mounts |
| ACRES Core model | ROS/acres_core_sim/test/test_core_sim_unit.cpp |
Ins.NoiseFreeEpochIsTheTruth, Ins.NoiseLevels |
- Test the model against an independent implementation or against published values.
- Test that the output without noise is the truth.
- Test that the standard deviation of the output agrees with the configuration.
-
Record two episodes with the same seed and compare the rows. They must be the same.
Packaged/Linux/Acres.sh -VehicleDemo -VehicleTest=turn -VehicleTerrainTest -SensorRecord -SensorOutput=/tmp/baro-a Packaged/Linux/Acres.sh -VehicleDemo -VehicleTest=turn -VehicleTerrainTest -SensorRecord -SensorOutput=/tmp/baro-b cmp /tmp/baro-a/episode-001/gnss.jsonl /tmp/baro-b/episode-001/gnss.jsonlExpected Result
cmpprints nothing. The two files of a stream of the physics thread are the same. -
Run the unit tests of the ROS 2 packages.
The option -VehicleTest=turn drives a fixed program of 17 s and then stops the game.
The Tests and Regression Checklist page lists all tests.
Write the Documentation#
- Write a model page in
Documentation/Modelswith the sections that Write Documentation gives. - Add the page to the navigation in
mkdocs.ymland to the table on the Conventions page. - Give each key in a parameter table with its type, unit, default and description.
- Add the sensor rig values to the Sensors and Rigs tutorial.
- Add each new term to the Glossary.
- Build the site and do the checks of Write Documentation.
The build generates three pages from the code: the option index, the menu settings and the header pages.
Thus the FParse calls, the menu rows and the /// comments of the new sensor appear on those pages after a build.
Checklist#
- The game stops with a clear message for each key that is out of its range.
- A file without the new block loads.
- The episode has the new
.jsonlfile, the count inepisode.jsonand zero dropped rows. - Two recordings with the same seed give the same rows.
-SensorNoNoisegives the truth and does not change the other streams.- The ROS 2 topic has the correct frame name, stamp and transform.
- A step request in lockstep returns after the data of the step.
- The unit tests, the language check and the strict build of the site pass.