Skip to content

Commit 992f3ef

Browse files
committed
Start BallTracker stuff
1 parent 7e87ab8 commit 992f3ef

File tree

6 files changed

+224
-6
lines changed

6 files changed

+224
-6
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ cpal = "0.15.3"
8989
dialoguer = "0.11.0"
9090
fast-math = "0.1.1"
9191
fast_image_resize = "5.0.0"
92+
filter = { version = "0.1.0", path = "crates/filter" }
9293
futures = "0.3.31"
9394
geo = "0.29.2"
9495
glob = "0.3.1"

tools/simulation/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ workspace = true
1515
bevy = { workspace = true }
1616
bevy_egui = { workspace = true }
1717
bifrost = { workspace = true }
18+
filter = { workspace = true }
1819
ml = { workspace = true }
1920
nalgebra = { workspace = true }
2021
nidhogg = { workspace = true }

tools/simulation/src/robot.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::time::Instant;
2+
13
use ::tasks::TaskPlugin;
24
use bevy::app::MainSchedulePlugin;
35
use bevy::ecs::event::EventRegistry;
@@ -20,15 +22,18 @@ use yggdrasil::localization::odometry::{update_odometry, Odometry};
2022
use yggdrasil::localization::RobotPose;
2123
use yggdrasil::motion::walking_engine::step_context::StepContext;
2224

25+
use yggdrasil::nao::Cycle;
2326
use yggdrasil::sensor::orientation::update_orientation;
24-
use yggdrasil::vision::ball_detection::classifier::init_ball_tracker;
27+
use yggdrasil::vision::ball_detection::ball_tracker::{BallPosition, BallTracker};
2528
use yggdrasil::vision::ball_detection::proposal::{BallProposalConfig, BallProposalConfigs};
2629
use yggdrasil::vision::ball_detection::BallDetectionConfig;
2730
use yggdrasil::vision::referee::communication::ReceivedRefereePose;
2831
use yggdrasil::vision::referee::recognize::RefereePoseRecognized;
2932
use yggdrasil::{behavior, kinematics, localization, motion, nao, schedule, sensor};
3033

3134
use bevy::ecs::schedule::ScheduleLabel;
35+
use filter::{CovarianceMatrix, StateTransform, StateVector, UnscentedKalmanFilter};
36+
use nalgebra::{point, Point2};
3237

3338
use crate::Simulation;
3439
// Real-world robot position in meters
@@ -139,13 +144,11 @@ pub fn robot_system(player_number: u8) -> SubApp {
139144
})
140145
.insert_resource(Whistle::default())
141146
.insert_resource(initial_gamecontroller())
147+
.insert_resource(ball_tracker_from_ball(&BallPosition(point![0.0, 0.0])))
142148
.add_event::<RefereePoseRecognized>()
143149
.add_event::<ReceivedRefereePose>()
144150
.add_event::<GameControllerMessageEvent>()
145-
.add_systems(
146-
PostStartup,
147-
(setup_robot, init_ball_tracker, update_orientation),
148-
)
151+
.add_systems(PostStartup, (setup_robot, update_orientation))
149152
.add_systems(
150153
PreUpdate,
151154
(update_simulated_odometry
@@ -194,6 +197,11 @@ pub fn robot_system(player_number: u8) -> SubApp {
194197
robot.set_position(robot_pose);
195198
}
196199
}
200+
201+
// Update the ball tracker with the ball position
202+
let ball = main_world.resource::<Simulation>().ball_position;
203+
let mut ball_tracker = sub_world.resource_mut::<BallTracker>();
204+
*ball_tracker = ball_tracker_from_ball(&ball);
197205
});
198206

199207
sub_app.update_schedule = Some(Main.intern());
@@ -233,3 +241,18 @@ fn initial_gamecontroller() -> GameControllerMessage {
233241
}; 2],
234242
}
235243
}
244+
245+
// A simulated version of the Balltracker with no noise and just the ball position
246+
fn ball_tracker_from_ball(ball: &BallPosition) -> BallTracker {
247+
BallTracker {
248+
position_kf: UnscentedKalmanFilter::<2, 5, BallPosition>::new(
249+
*ball,
250+
CovarianceMatrix::from_diagonal_element(0.000005),
251+
),
252+
prediction_noise: CovarianceMatrix::from_diagonal_element(0.000005),
253+
sensor_noise: CovarianceMatrix::from_diagonal_element(0.000005),
254+
cycle: Cycle::default(),
255+
timestamp: Instant::now(),
256+
stationary_variance_threshold: 0.000005,
257+
}
258+
}

tools/simulation/src/sim_lola.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use bevy::{app::PluginGroupBuilder, ecs::system::RunSystemOnce, prelude::*};
2+
use nidhogg::{
3+
types::{FillExt, JointArray},
4+
DisconnectExt, HardwareInfo, NaoBackend, NaoControlMessage, NaoState, Result,
5+
};
6+
7+
use nidhogg::backend::{ConnectWithRetry, ReadHardwareInfo};
8+
use yggdrasil::motion::keyframe::manager;
9+
10+
const DEFAULT_STIFFNESS: f32 = 0.8;
11+
12+
pub struct SimulationNaoPlugins;
13+
14+
impl PluginGroup for SimulationNaoPlugins {
15+
fn build(self) -> PluginGroupBuilder {
16+
PluginGroupBuilder::start::<Self>()
17+
.add(SimLolaPlugin)
18+
.add(cycle::CycleTimePlugin)
19+
.add(battery_led::BatteryLedPlugin)
20+
.add(manager::NaoManagerPlugin)
21+
.add(center_of_mass::CenterOfMassPlugin)
22+
.add(center_of_pressure::CenterOfPressurePlugin)
23+
}
24+
}
25+
26+
/// Plugin that adds systems for reading and writing to the `LoLA` socket using [`nidhogg`].
27+
pub(super) struct SimLolaPlugin;
28+
29+
impl Plugin for SimLolaPlugin {
30+
fn build(&self, app: &mut App) {
31+
app.init_resource::<NaoControlMessage>();
32+
33+
app.world_mut()
34+
.run_system_once(setup_lola)
35+
.expect("failed to setup lola!");
36+
app.world_mut()
37+
.run_system_once(initialize_nao)
38+
.expect("failed to initialize nao resources!");
39+
40+
app.add_systems(Write, sync_hardware);
41+
}
42+
}
43+
44+
/// Resource containing the [`SimLolaBackend`].
45+
#[derive(Resource, Debug, Deref, DerefMut)]
46+
pub struct SimLola(SimLolaBackend);
47+
48+
fn setup_lola(mut commands: Commands) {
49+
commands.insert_resource(SimLola(SimLolaBackend));
50+
}
51+
52+
fn initialize_nao(mut commands: Commands, mut lola: ResMut<SimLola>) {
53+
// let info = RobotInfo::new(&mut lola.0).expect("failed to read robot info from LoLA");
54+
55+
let info = RobotInfo {
56+
robot_name: "local".to_string(),
57+
robot_id: 0,
58+
head_id: Default::default(),
59+
head_version: Default::default(),
60+
body_id: Default::default(),
61+
body_version: Default::default(),
62+
initial_joint_positions: Default::default(),
63+
};
64+
65+
// Read state and reply with a message.
66+
let state = lola
67+
.read_nao_state()
68+
.expect("failed to read initial state from LoLA");
69+
let msg = NaoControlMessage {
70+
position: info.initial_joint_positions.clone(),
71+
stiffness: JointArray::fill(DEFAULT_STIFFNESS),
72+
..Default::default()
73+
};
74+
lola.send_control_msg(msg)
75+
.expect("failed to send initial control message to LoLA");
76+
77+
tracing::info!(
78+
"Launched yggdrasil on {} with head_id: {}, body_id: {}",
79+
info.robot_name,
80+
info.head_id,
81+
info.body_id
82+
);
83+
84+
tracing::info!("Battery level: {}", state.battery.charge);
85+
86+
commands.insert_resource(state);
87+
commands.insert_resource(info);
88+
}
89+
90+
pub fn sync_hardware(
91+
mut nao: ResMut<SimLola>,
92+
mut robot_state: ResMut<NaoState>,
93+
update: Res<NaoControlMessage>,
94+
) {
95+
nao.send_control_msg(update.clone())
96+
.expect("failed to send control message to LoLA");
97+
98+
*robot_state = nao
99+
.read_nao_state()
100+
.expect("failed to read state from LoLA");
101+
}
102+
103+
/// `LoLA` backend that communicates with a real NAO V6 through the socket at `/tmp/robocup`
104+
#[derive(Debug)]
105+
pub struct SimLolaBackend;
106+
107+
impl NaoBackend for SimLolaBackend {
108+
/// Connects to a NAO backend
109+
///
110+
/// # Examples
111+
/// ```no_run
112+
/// use nidhogg::{NaoBackend, backend::SimLolaBackend};
113+
///
114+
/// // We connect to a real NAO using the `LoLA` backend
115+
/// let mut nao = SimLolaBackend::connect().expect("Could not connect to the NAO! 😪");
116+
/// ```
117+
fn connect() -> Result<Self> {
118+
Ok(SimLolaBackend)
119+
}
120+
121+
/// Converts a control message to the format required by the backend and writes it to that backend.
122+
///
123+
/// # Examples
124+
/// ```no_run
125+
/// use nidhogg::{NaoBackend, NaoControlMessage, backend::SimLolaBackend, types::color};
126+
///
127+
/// let mut nao = SimLolaBackend::connect().unwrap();
128+
///
129+
/// // First, create a new control message where we set the chest color
130+
/// let msg = NaoControlMessage::builder().chest(color::f32::MAGENTA).build();
131+
///
132+
/// // Now we send it to the NAO!
133+
/// nao.send_control_msg(msg).expect("Failed to write control message to backend!");
134+
/// ```
135+
fn send_control_msg(
136+
&mut self,
137+
_: NaoControlMessage,
138+
) -> std::result::Result<(), nidhogg::Error> {
139+
Ok(())
140+
}
141+
142+
/// Reads the current sensor data from the chosen backend
143+
///
144+
/// # Examples
145+
/// ```no_run
146+
/// use nidhogg::{NaoBackend, backend::SimLolaBackend};
147+
///
148+
/// let mut nao = SimLolaBackend::connect().unwrap();
149+
///
150+
/// // Get the current state of the robot
151+
/// let state = nao.read_nao_state().expect("Failed to retrieve sensor data!");
152+
/// ```
153+
fn read_nao_state(&mut self) -> Result<NaoState> {
154+
Ok(NaoState {
155+
position: Default::default(),
156+
stiffness: Default::default(),
157+
accelerometer: Default::default(),
158+
gyroscope: Default::default(),
159+
angles: Default::default(),
160+
sonar: Default::default(),
161+
fsr: Default::default(),
162+
touch: Default::default(),
163+
battery: Default::default(),
164+
temperature: Default::default(),
165+
current: Default::default(),
166+
status: Default::default(),
167+
})
168+
}
169+
}
170+
171+
impl DisconnectExt for SimLolaBackend {
172+
fn disconnect(self) -> Result<()> {
173+
Ok(())
174+
}
175+
}
176+
177+
impl ConnectWithRetry for SimLolaBackend {}
178+
179+
impl ReadHardwareInfo for SimLolaBackend {
180+
fn read_hardware_info(&mut self) -> Result<HardwareInfo> {
181+
Ok(HardwareInfo {
182+
body_id: Default::default(),
183+
body_version: Default::default(),
184+
head_id: Default::default(),
185+
head_version: Default::default(),
186+
})
187+
}
188+
}

tools/simulation/src/simulation.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use bevy::{prelude::*, window::PrimaryWindow};
22
use bifrost::communication::GameState;
3+
use nalgebra::point;
34
use yggdrasil::core::config::layout::LayoutConfig;
45
use yggdrasil::prelude::Config;
6+
use yggdrasil::vision::ball_detection::ball_tracker::BallPosition;
57

68
use crate::robot::Robot;
79
use crate::{FIELD_HEIGHT_METERS, FIELD_WIDTH_METERS, ROBOT_SIZE_METERS};
@@ -19,13 +21,15 @@ pub struct PositionCircle;
1921

2022
#[derive(Resource)]
2123
pub struct Simulation {
24+
pub ball_position: BallPosition,
2225
pub state: GameState,
2326
pub pixels_per_meter: f32,
2427
}
2528

2629
impl Default for Simulation {
2730
fn default() -> Self {
2831
Self {
32+
ball_position: BallPosition(point![3.0, 1.0]),
2933
state: GameState::Initial,
3034
pixels_per_meter: PIXELS_PER_METER,
3135
}

0 commit comments

Comments
 (0)