Skip to content

Commit e2c724f

Browse files
tobifalkcassava
authored andcommitted
vtd: Add external ego model class
1 parent 7291adb commit e2c724f

File tree

5 files changed

+260
-26
lines changed

5 files changed

+260
-26
lines changed

optional/vtd/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ if(BUILD_TESTING)
6262
src/rdb_transceiver_tcp_test.cpp
6363
src/osi_test.cpp
6464
src/vtd_osi_test.cpp
65+
src/vtd_data_conversion_test.cpp
6566
)
6667
set_target_properties(test-vtd-binding PROPERTIES
6768
CXX_STANDARD 14

optional/vtd/src/actuator_component.hpp

+59-6
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727
#include <memory> // for shared_ptr<>
2828

29-
#include <cloe/component/latlong_actuator.hpp> // for LatLongActuator
29+
#include <cloe/component/latlong_actuator.hpp> // for LatLongActuator
30+
#include <cloe/component/vehicle_state_model.hpp> // for VehicleStateModel
3031

3132
#include "task_control.hpp" // for TaskControl
3233
#include "vtd_logger.hpp" // for vtd_logger
@@ -157,21 +158,73 @@ class VtdLatLongActuator : public VtdVehicleControl, public cloe::LatLongActuato
157158
cloe::utility::ActuationLevel old_level_;
158159
};
159160

161+
/**
162+
* VtdExternalEgoModel provides the ego state from an external model to VTD.
163+
*
164+
* # Usage
165+
*
166+
* Every VTD cycle, the following needs to be done:
167+
*
168+
* - `step_begin` retrieves the updated ego vehicle state from the external model
169+
* and registers the new state with the TaskControl client.
170+
* - `TaskControl::add_trigger_and_send` must be called to send the information
171+
* to VTD.
172+
*/
173+
class VtdExternalEgoModel : public VtdVehicleControl, public cloe::VehicleStateModel {
174+
public:
175+
VtdExternalEgoModel(std::shared_ptr<TaskControl> tc, uint64_t id, const std::string& veh_name)
176+
: VehicleStateModel("vtd/ego_state")
177+
, task_control_(tc)
178+
, vehicle_id_(id)
179+
, vehicle_name_(veh_name) {}
180+
virtual ~VtdExternalEgoModel() = default;
181+
182+
/**
183+
* Add the DynObjectState package to the TaskControl.
184+
*
185+
* This should only be called once per simulation step. This method will not
186+
* pay attention for you. Later, when the TaskControl sends its packages,
187+
* this one will be part of it.
188+
*/
189+
void step_begin(const cloe::Sync& sync) override {
190+
if (this->is_vehicle_state()) {
191+
add_dyn_object_state();
192+
} else if (sync.step() > 1) {
193+
// During the first time step, external model has not yet been updated. Throw otherwise.
194+
throw cloe::ModelError("VtdExternalEgoModel: vehicle state not set.");
160195
}
161196
}
162197

163-
cloe::Duration process(const cloe::Sync& sync) override { return LatLongActuator::process(sync); }
164-
165198
void reset() override {
166-
old_level_.set_none();
167-
LatLongActuator::reset();
199+
VehicleStateModel::reset();
168200
task_control_->reset();
169201
}
170202

203+
cloe::Json to_json() const override {
204+
return dynamic_cast<const cloe::VehicleStateModel&>(*this);
205+
}
206+
207+
private:
208+
void add_dyn_object_state() {
209+
auto ego_state = this->vehicle_state();
210+
assert(ego_state);
211+
DynObjectState os;
212+
assert(static_cast<uint64_t>(ego_state->id) == vehicle_id_);
213+
os.base_id = ego_state->id;
214+
os.base_type = cloe_vtd_obj_class_map.at(ego_state->classification);
215+
os.base_name = vehicle_name_;
216+
os.base_geo = rdb_geometry_from_object(ego_state.get());
217+
os.base_pos = rdb_coord_from_object(ego_state.get());
218+
os.ext_speed = rdb_coord_from_vector3d(ego_state->velocity, ego_state->angular_velocity);
219+
os.ext_accel = rdb_coord_pos_from_vector3d(ego_state->acceleration);
220+
// Add new ego state to task control message.
221+
task_control_->add_dyn_object_state(os);
222+
}
223+
171224
private:
172225
std::shared_ptr<TaskControl> task_control_;
173226
uint64_t vehicle_id_;
174-
cloe::utility::ActuationLevel old_level_;
227+
std::string vehicle_name_;
175228
};
176229

177230
} // namespace vtd

optional/vtd/src/task_control.hpp

+116-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
#include <VtdToolkit/RDBHandler.hh>
3232
#endif
3333

34-
#include <cloe/core.hpp> // for Json
34+
#include <cloe/component/object.hpp> // for Object
35+
#include <cloe/core.hpp> // for Json
3536

3637
#include "omni_sensor_component.hpp" // for VtdOmniSensor
3738
#include "rdb_codec.hpp" // for RdbCodec
@@ -77,6 +78,97 @@ struct DriverControl {
7778
}
7879
};
7980

81+
struct DynObjectState {
82+
/// Object id.
83+
uint32_t base_id{0};
84+
85+
/// Object category (player, sensor, ...).
86+
uint8_t base_category{RDB_OBJECT_CATEGORY_PLAYER};
87+
88+
/// Object type (car, truck, ...).
89+
uint8_t base_type{RDB_OBJECT_TYPE_NONE};
90+
91+
/// Visibility mask (e.g. visible for traffic and visible for data recorder).
92+
uint16_t base_vis_mask{RDB_OBJECT_VIS_FLAG_TRAFFIC | RDB_OBJECT_VIS_FLAG_RECORDER};
93+
94+
/// Player name.
95+
std::string base_name;
96+
97+
/// Object dimension and offset to cog.
98+
RDB_GEOMETRY_t base_geo;
99+
100+
/// Object position and orientation.
101+
RDB_COORD_t base_pos;
102+
103+
/// Object velocity and angular velocity.
104+
RDB_COORD_t ext_speed;
105+
106+
/// Object acceleration and angular acceleration.
107+
RDB_COORD_t ext_accel;
108+
109+
friend void to_json(cloe::Json& j, const DynObjectState& os) {
110+
j = cloe::Json{
111+
{"base_id", os.base_id}, {"base_category", os.base_category},
112+
{"base_type", os.base_type}, {"base_vis_mask", os.base_vis_mask},
113+
{"base_name", os.base_name},
114+
};
115+
}
116+
};
117+
118+
/**
119+
* Map to convert from Cloe to VTD object classification.
120+
*/
121+
const std::map<cloe::Object::Class, uint8_t> cloe_vtd_obj_class_map = {
122+
{cloe::Object::Class::Car, RDB_OBJECT_TYPE_PLAYER_CAR},
123+
{cloe::Object::Class::Truck, RDB_OBJECT_TYPE_PLAYER_TRUCK},
124+
{cloe::Object::Class::Motorbike, RDB_OBJECT_TYPE_PLAYER_MOTORBIKE},
125+
{cloe::Object::Class::Trailer, RDB_OBJECT_TYPE_PLAYER_TRAILER},
126+
};
127+
128+
/**
129+
* Convert object geometry VTD geometry.
130+
*/
131+
RDB_GEOMETRY_t rdb_geometry_from_object(const cloe::Object& obj) {
132+
RDB_GEOMETRY_t geo;
133+
geo.dimX = obj.dimensions.x();
134+
geo.dimY = obj.dimensions.y();
135+
geo.dimZ = obj.dimensions.z();
136+
geo.offX = obj.cog_offset.x();
137+
geo.offY = obj.cog_offset.y();
138+
geo.offZ = obj.cog_offset.z();
139+
return geo;
140+
}
141+
142+
RDB_COORD_t rdb_coord_from_vector3d(const Eigen::Vector3d& position,
143+
const Eigen::Vector3d& angle_rph) {
144+
RDB_COORD_t coord;
145+
coord.x = position.x();
146+
coord.y = position.y();
147+
coord.z = position.z();
148+
coord.r = angle_rph.x();
149+
coord.p = angle_rph.y();
150+
coord.h = angle_rph.z();
151+
coord.flags = RDB_COORD_FLAG_POINT_VALID | RDB_COORD_FLAG_ANGLES_VALID;
152+
coord.type = RDB_COORD_TYPE_INERTIAL;
153+
return coord;
154+
}
155+
156+
RDB_COORD_t rdb_coord_from_object(const cloe::Object& obj) {
157+
Eigen::Vector3d hpr = obj.pose.rotation().matrix().eulerAngles(2, 1, 0);
158+
return rdb_coord_from_vector3d(obj.pose.translation(),
159+
Eigen::Vector3d(hpr.z(), hpr.y(), hpr.x()));
160+
}
161+
162+
RDB_COORD_t rdb_coord_pos_from_vector3d(const Eigen::Vector3d& position) {
163+
RDB_COORD_t coord;
164+
coord.x = position.x();
165+
coord.y = position.y();
166+
coord.z = position.z();
167+
coord.flags = RDB_COORD_FLAG_POINT_VALID;
168+
coord.type = RDB_COORD_TYPE_INERTIAL;
169+
return coord;
170+
}
171+
80172
/**
81173
* TaskControl contains the connection to the VTD task control server.
82174
*
@@ -172,6 +264,29 @@ class TaskControl : public VtdOmniSensor {
172264
driverCtrl->validityFlags = dc.validity_flags;
173265
}
174266

267+
void add_dyn_object_state(const DynObjectState& os) {
268+
// TODO(tobias): From the implementation of `add_driver_control`, it seems
269+
// that the actual sim time and frame no. are not needed..
270+
handler_.addPackage(0.0, 0, RDB_PKG_ID_START_OF_FRAME);
271+
RDB_OBJECT_STATE_t* objState = reinterpret_cast<RDB_OBJECT_STATE_t*>(
272+
handler_.addPackage(0.0, 0, RDB_PKG_ID_OBJECT_STATE, /*noElements=*/1,
273+
/*extended=*/true));
274+
if (objState == nullptr) {
275+
vtd_logger()->error("TaskControl: cannot add RDB_OBJECT_STATE package");
276+
return;
277+
}
278+
objState->base.id = os.base_id;
279+
objState->base.category = os.base_category;
280+
objState->base.type = os.base_type;
281+
objState->base.visMask = os.base_vis_mask;
282+
std::strcpy(objState->base.name, os.base_name.c_str());
283+
objState->base.geo = os.base_geo;
284+
objState->base.pos = os.base_pos;
285+
objState->ext.speed = os.ext_speed;
286+
objState->ext.accel = os.ext_accel;
287+
handler_.addPackage(0.0, 0, RDB_PKG_ID_END_OF_FRAME);
288+
}
289+
175290
/**
176291
* Add the trigger package, which specifies how much VTD should step.
177292
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2021 Robert Bosch GmbH
3+
4+
*/
5+
/**
6+
* \file vtd_data_conversion_test.cpp
7+
* \see omni_sensor_component.hpp
8+
* \see task_control.hpp
9+
*/
10+
11+
#include <cmath> // for M_PI
12+
13+
#include <gtest/gtest.h> // for TEST, ASSERT_TRUE
14+
#include <cloe/component/object.hpp> // for Object
15+
16+
#include "omni_sensor_component.hpp"
17+
#include "task_control.hpp"
18+
19+
RDB_COORD_t get_test_rdb_coord() {
20+
RDB_COORD_t c;
21+
c.x = 1.0;
22+
c.y = 2.0;
23+
c.z = 3.0;
24+
c.h = 0.1 * M_PI;
25+
c.p = 0.2 * M_PI;
26+
c.r = 0.3 * M_PI;
27+
c.flags = RDB_COORD_FLAG_POINT_VALID | RDB_COORD_FLAG_ANGLES_VALID;
28+
return c;
29+
}
30+
31+
void assert_rdb_coord_eq(const RDB_COORD_t& c1, const RDB_COORD_t& c2) {
32+
ASSERT_DOUBLE_EQ(c1.x, c2.x);
33+
ASSERT_DOUBLE_EQ(c1.y, c2.y);
34+
ASSERT_DOUBLE_EQ(c1.z, c2.z);
35+
if (c2.flags & RDB_COORD_FLAG_ANGLES_VALID) {
36+
ASSERT_DOUBLE_EQ(c1.h, c2.h);
37+
ASSERT_DOUBLE_EQ(c1.p, c2.p);
38+
ASSERT_DOUBLE_EQ(c1.r, c2.r);
39+
}
40+
}
41+
42+
TEST(vtd_data, rdb_coord) {
43+
// Convert from VTD to Cloe data and back.
44+
RDB_COORD_t coord = get_test_rdb_coord();
45+
cloe::Object obj;
46+
obj.pose = vtd::from_vtd_pose(coord);
47+
RDB_COORD_t coord2 = vtd::rdb_coord_from_object(obj);
48+
assert_rdb_coord_eq(coord, coord2);
49+
coord2 = vtd::rdb_coord_pos_from_vector3d(obj.pose.translation());
50+
assert_rdb_coord_eq(coord, coord2);
51+
}

optional/vtd/src/vtd_vehicle.hpp

+33-19
Original file line numberDiff line numberDiff line change
@@ -230,28 +230,42 @@ class VtdVehicle : public cloe::Vehicle {
230230
for (const auto& comp : components) {
231231
auto name = comp.first;
232232
auto cfg = comp.second;
233-
if (!sensors_.count(cfg.from)) {
234-
throw cloe::ModelError("reference to unknown sensor '{}'", cfg.from);
235-
}
236-
std::shared_ptr<VtdSensorData> sensor;
237-
if (cfg.from != "task_control") {
238-
sensor = this->sensors_.at(cfg.from);
239-
}
240-
auto new_or_override_component = [this](cloe::Component* c, std::string name, bool override) {
241-
if (override) {
242-
this->emplace_component(std::shared_ptr<cloe::Component>(c), name);
233+
if (cfg.from.empty()) {
234+
// Configure actuators.
235+
if (cfg.type == "ego_state_model") {
236+
auto ego_model = std::make_shared<VtdExternalEgoModel>(task_control_, id_, vtd_name_);
237+
// Since no default component of this type was instantiated in the constructor, cfg.override does not apply.
238+
this->add_component(ego_model, name);
239+
ego_control_ = ego_model;
243240
} else {
244-
this->add_component(std::shared_ptr<cloe::Component>(c), name);
241+
throw cloe::ModelError("unknown actuator component type '{}'", cfg.type);
245242
}
246-
};
247-
if (cfg.type == "lane_sensor") {
248-
new_or_override_component(new VtdLaneBoundarySensor{sensor}, name, cfg.override);
249-
} else if (cfg.type == "object_sensor") {
250-
new_or_override_component(new VtdWorldSensor{sensor}, name, cfg.override);
251-
} else if (cfg.type == "driver_request") {
252-
new_or_override_component(new VtdDriverRequest{id_, task_control_}, name, cfg.override);
253243
} else {
254-
throw cloe::ModelError("unknown component type '{}'", cfg.type);
244+
// Configure sensors.
245+
std::shared_ptr<VtdSensorData> sensor;
246+
if (cfg.from != "task_control") {
247+
if (!sensors_.count(cfg.from)) {
248+
throw cloe::ModelError("reference to unknown sensor '{}'", cfg.from);
249+
}
250+
sensor = this->sensors_.at(cfg.from);
251+
}
252+
auto new_or_override_component = [this](cloe::Component* c, std::string name,
253+
bool override) {
254+
if (override) {
255+
this->emplace_component(std::shared_ptr<cloe::Component>(c), name);
256+
} else {
257+
this->add_component(std::shared_ptr<cloe::Component>(c), name);
258+
}
259+
};
260+
if (cfg.type == "lane_sensor") {
261+
new_or_override_component(new VtdLaneBoundarySensor{sensor}, name, cfg.override);
262+
} else if (cfg.type == "object_sensor") {
263+
new_or_override_component(new VtdWorldSensor{sensor}, name, cfg.override);
264+
} else if (cfg.type == "driver_request") {
265+
new_or_override_component(new VtdDriverRequest{id_, task_control_}, name, cfg.override);
266+
} else {
267+
throw cloe::ModelError("unknown sensor component type '{}'", cfg.type);
268+
}
255269
}
256270
}
257271
}

0 commit comments

Comments
 (0)