-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathnode.rs
More file actions
299 lines (279 loc) · 11.1 KB
/
Copy pathnode.rs
File metadata and controls
299 lines (279 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
use std::{future::Future, pin::Pin, sync::Arc, time::Duration};
use booster::ImuState;
use color_eyre::{
Result,
eyre::{Context as _, bail},
};
use coordinate_systems::{Field, Robot};
use linear_algebra::Isometry3;
use localization_factrs::{InitialState, initialize};
use projection::{camera_matrix::CameraMatrix, intrinsic::Intrinsic};
use ros_z::{
cache::Cache,
context::Context,
parameter::NodeParametersExt,
qos::{QosDurability, QosProfile},
};
use tokio::select;
use types::{
field_dimensions::FieldDimensions,
primary_state::PrimaryState,
time_wrapper::TimeWrapper,
visual_localization::{
ASSOCIATION_POSE_HINT_TOPIC, AssociationPoseHint, LOCALIZATION_POSE_3D_TOPIC,
VISUAL_LOCALIZATION_TOPIC, VisualLocalizationFrame,
},
visual_odometry::{VisualOdometer, VisualOdometryDelta as VisualOdometryDeltaMessage},
};
use crate::{
backend_task::spawn_backend_task,
damping::{
initial_state_for_reset, localization_is_damping, publish_damping_optimization_result,
reset_and_publish_startup_prior,
},
diagnostics::SolveDiagnostics,
event_handlers::{handle_optimization_result, handle_visual_odometer, handle_visual_odometry},
ingest::ingest_foot_heights,
live_odometry::LiveVisualOdometryLocalization,
parameters::{
Localization3dParameters, backend_configuration_from_parameters_and_field_dimensions,
},
pose::initial_state_from_camera_matrix,
publish::LocalizationPublishers,
visual_localization::{GlobalVisualLock, handle_visual_localization_frame},
};
const VISUAL_ODOMETER_TOPIC: &str = "visual_odometry/current_left_camera_to_visual_odometer";
/// Starts the localization node and erases the concrete future type for node runners.
///
/// `ctx` is the ROS-Z context used to create publishers, subscribers, caches, and parameters.
pub fn run_boxed(ctx: Arc<Context>) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
Box::pin(run(ctx))
}
/// Runs the asynchronous 3D localization node until its input streams terminate or fail.
///
/// The node consumes IMU, camera matrix, field-mark associations, visual odometry, and kinematics
/// topics, then publishes the optimized robot pose and debug streams.
pub async fn run(ctx: Arc<Context>) -> Result<()> {
let node = ctx.create_node("localization3d").build().await?;
let parameters = node.bind_parameter_as::<Localization3dParameters>("localization3d")?;
parameters.add_validation_hook(Localization3dParameters::validate)?;
let imu_subscriber = node
.subscriber::<ImuState>("inputs/imu_state")
.build()
.await?;
let camera_matrix_cache = node
.subscriber::<TimeWrapper<CameraMatrix>>("camera_matrix")
.cache(128)
.with_stamp(|message| message.time)
.build()
.await?;
let field_dimensions_cache = node
.subscriber::<FieldDimensions>("field_dimensions")
.qos(QosProfile {
durability: QosDurability::TransientLocal,
..Default::default()
})
.cache(1)
.build()
.await?;
let visual_localization_subscriber = node
.subscriber::<TimeWrapper<VisualLocalizationFrame>>(VISUAL_LOCALIZATION_TOPIC)
.build()
.await?;
let visual_odometry_subscriber = node
.subscriber::<VisualOdometryDeltaMessage>(
"visual_odometry/current_left_camera_to_previous_left_camera",
)
.build()
.await?;
let visual_odometer_cache = node
.subscriber::<VisualOdometer>(VISUAL_ODOMETER_TOPIC)
.cache(128)
.with_stamp(|message| message.time)
.build()
.await?;
let visual_odometer_subscriber = node
.subscriber::<VisualOdometer>(VISUAL_ODOMETER_TOPIC)
.build()
.await?;
let robot_kinematics_subscriber = node
.subscriber::<TimeWrapper<kinematics::robot_kinematics::RobotKinematics>>(
"robot_kinematics",
)
.build()
.await?;
let primary_state_cache = node
.subscriber::<PrimaryState>("primary_state")
.qos(QosProfile {
durability: QosDurability::TransientLocal,
..Default::default()
})
.cache(1)
.build()
.await?;
let localization_publisher = node
.publisher::<Option<Isometry3<Field, Robot>>>("localization")
.build()
.await?;
let pose_3d_publisher = node
.publisher::<TimeWrapper<Option<Isometry3<Field, Robot>>>>(LOCALIZATION_POSE_3D_TOPIC)
.build()
.await?;
let association_pose_hint_publisher = node
.publisher::<TimeWrapper<Option<AssociationPoseHint>>>(ASSOCIATION_POSE_HINT_TOPIC)
.build()
.await?;
let calibrated_intrinsics_publisher = node
.publisher::<Intrinsic>("debug/calibrated_intrinsics")
.build()
.await?;
let solve_diagnostics_publisher = node
.publisher::<TimeWrapper<SolveDiagnostics>>("debug/solve_diagnostics")
.build()
.await?;
let field_dimensions = wait_for_field_dimensions(&field_dimensions_cache).await;
let initial_state = wait_for_initial_state(&camera_matrix_cache, &field_dimensions).await;
let localization_parameters = parameters.snapshot().typed().clone();
let (mut frontend, backend) = initialize(
backend_configuration_from_parameters_and_field_dimensions(
&localization_parameters,
&field_dimensions,
),
initial_state.clone(),
);
let mut backend_handle =
std::pin::pin!(spawn_backend_task(backend, solve_diagnostics_publisher));
let mut live_localization = LiveVisualOdometryLocalization::default();
let mut global_visual_lock = GlobalVisualLock::Unlocked;
let mut damping_reset_interval = tokio::time::interval(Duration::from_millis(100));
let publishers = LocalizationPublishers::new(
&localization_publisher,
&pose_3d_publisher,
&association_pose_hint_publisher,
);
loop {
select! {
_ = damping_reset_interval.tick() => {
if !localization_is_damping(&primary_state_cache) {
continue;
}
let now = node.clock().now();
reset_and_publish_startup_prior(
&mut frontend,
&mut live_localization,
&mut global_visual_lock,
initial_state_for_reset(&camera_matrix_cache, &field_dimensions, &initial_state),
now,
&field_dimensions,
publishers,
)
.await
.wrap_err("failed to reset localization while damping")?;
}
visual_localization = visual_localization_subscriber.recv() => {
let visual_localization = visual_localization?;
if should_drop_input_while_damping(&primary_state_cache) {
continue;
}
handle_visual_localization_frame(
&mut frontend,
&mut live_localization,
&mut global_visual_lock,
visual_localization,
)
.wrap_err("failed to ingest visual localization frame")?;
}
// IMU payloads have no sensor timestamp; ros-z source time is the aligned clock.
imu = imu_subscriber.recv_with_metadata() => {
let imu = imu?;
if should_drop_input_while_damping(&primary_state_cache) {
continue;
}
frontend.ingest_imu(imu.source_time.to_wallclock(), imu.message)
.wrap_err("failed to ingest imu measurement into frontend")?;
}
visual_odometry = visual_odometry_subscriber.recv() => {
let visual_odometry = visual_odometry?;
if should_drop_input_while_damping(&primary_state_cache) {
continue;
}
handle_visual_odometry(&mut frontend, visual_odometry, &camera_matrix_cache)?;
}
visual_odometer = visual_odometer_subscriber.recv() => {
let visual_odometer = visual_odometer?;
if should_drop_input_while_damping(&primary_state_cache) {
continue;
}
handle_visual_odometer(
&mut live_localization,
global_visual_lock,
visual_odometer,
&visual_odometer_cache,
&camera_matrix_cache,
publishers,
).await?;
}
robot_kinematics = robot_kinematics_subscriber.recv() => {
let robot_kinematics = robot_kinematics?;
if should_drop_input_while_damping(&primary_state_cache) {
continue;
}
ingest_foot_heights(&mut frontend, robot_kinematics)
.wrap_err("failed to ingest foot height measurement into frontend")?;
}
result = &mut backend_handle => {
result.wrap_err("failed to join")?.wrap_err("solver failed")?;
bail!("solver stopped unexpectedly");
}
result = frontend.wait_for_optimization_result() => {
result?;
if localization_is_damping(&primary_state_cache) {
publish_damping_optimization_result(
&mut frontend,
&mut live_localization,
&mut global_visual_lock,
node.clock().now(),
&field_dimensions,
publishers,
).await?;
continue;
}
handle_optimization_result(
&mut frontend,
&mut live_localization,
&mut global_visual_lock,
&visual_odometer_cache,
&camera_matrix_cache,
publishers,
&calibrated_intrinsics_publisher,
).await?;
}
}
}
}
fn should_drop_input_while_damping(primary_state_cache: &Cache<PrimaryState>) -> bool {
localization_is_damping(primary_state_cache)
}
async fn wait_for_initial_state(
camera_matrix_cache: &Cache<TimeWrapper<CameraMatrix>>,
field_dimensions: &FieldDimensions,
) -> InitialState {
let mut interval = tokio::time::interval(Duration::from_millis(10));
loop {
if let Some(camera_matrix) = camera_matrix_cache.get_latest() {
return initial_state_from_camera_matrix(&camera_matrix.inner, field_dimensions);
}
interval.tick().await;
}
}
async fn wait_for_field_dimensions(
field_dimensions_cache: &Cache<FieldDimensions>,
) -> FieldDimensions {
let mut interval = tokio::time::interval(Duration::from_millis(10));
loop {
if let Some(field_dimensions) = field_dimensions_cache.get_latest() {
return *field_dimensions.as_ref();
}
interval.tick().await;
}
}