Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
bb7b1f5
Added ball communication (global and local coordinates still need to …
marinaog Apr 23, 2025
06d696c
Transformation local<->global coordinates added
marinaog Apr 23, 2025
577b56c
Typo fixed
marinaog Apr 23, 2025
89037d8
fromat
juliablbr Apr 23, 2025
9bc628a
should be fine eh
juliablbr Apr 23, 2025
b32b8d4
shut up clippy
juliablbr Apr 23, 2025
4720f6b
weeeee
juliablbr Apr 23, 2025
0ae3e2a
save ball position
oxkitsune Apr 23, 2025
e8a337f
stuff
oxkitsune Apr 23, 2025
3bff0f8
fixing clippy
marinaog May 19, 2025
612106b
merging conflict
marinaog May 19, 2025
c0985d0
Merge branch 'main' into marinita/final_ball_communication
marinaog May 19, 2025
460e64e
clippy me this one
marinaog May 19, 2025
cc7e7c4
Merge branch 'marinita/final_ball_communication' of https://github.co…
marinaog May 19, 2025
089fcd6
Removed print check
marinaog May 21, 2025
327454c
cargo fmt doesnt forgive
marinaog May 21, 2025
ea2f4d8
Merge branch 'main' into marinita/final_ball_communication
juellsprott Jul 16, 2025
3840eb4
Merge branch 'main' into marinita/final_ball_communication
juellsprott Jul 16, 2025
41e33b6
timeout team ball after constant amount of cycles without communicate…
juellsprott Jul 17, 2025
b529862
formatting
juellsprott Jul 17, 2025
44a12b6
remove print statements
juellsprott Jul 17, 2025
9531ff9
test balltracker crate addition
juellsprott Jul 17, 2025
4e40ac8
remove balltracker import
juellsprott Jul 17, 2025
0a2e8ad
change team ball to detected ball in striker.rs
juellsprott Jul 17, 2025
305eb26
Merge branch 'main' into marinita/final_ball_communication
juellsprott Jul 18, 2025
0ca9331
fix striker.rs not being merged with main
juellsprott Jul 18, 2025
51712e6
remove cycle time thresholding on communicated ball message age
juellsprott Jul 18, 2025
7adc8a7
merge with main
juellsprott Jul 18, 2025
0cf29bc
fix imports
juellsprott Jul 18, 2025
aabf91f
rebase
juellsprott Jul 19, 2025
4af0a6b
Transformation local<->global coordinates added
marinaog Apr 23, 2025
28d86fe
Typo fixed
marinaog Apr 23, 2025
78e71bb
fromat
juliablbr Apr 23, 2025
305890e
rebase
juellsprott Jul 19, 2025
f95a6a8
shut up clippy
juliablbr Apr 23, 2025
00f00a7
weeeee
juliablbr Apr 23, 2025
8dc9874
rebase
juellsprott Jul 19, 2025
af83206
stuff
oxkitsune Apr 23, 2025
be600c8
fixing clippy
marinaog May 19, 2025
49de75e
merging conflict
marinaog May 19, 2025
f83d0bc
clippy me this one
marinaog May 19, 2025
b1be802
Removed print check
marinaog May 21, 2025
1354800
cargo fmt doesnt forgive
marinaog May 21, 2025
05a4c42
timeout team ball after constant amount of cycles without communicate…
juellsprott Jul 17, 2025
c7cc8e8
formatting
juellsprott Jul 17, 2025
e994c17
remove print statements
juellsprott Jul 17, 2025
a3a1110
test balltracker crate addition
juellsprott Jul 17, 2025
9ac7140
change team ball to detected ball in striker.rs
juellsprott Jul 17, 2025
b401e97
fix striker.rs not being merged with main
juellsprott Jul 18, 2025
130a91f
remove cycle time thresholding on communicated ball message age
juellsprott Jul 18, 2025
f380c44
fix imports
juellsprott Jul 18, 2025
eb3bcc9
rebase
juellsprott Jul 19, 2025
6d13614
rebase
juellsprott Jul 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions crates/bifrost/src/serialization/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,44 @@ where
}
}

impl<T> Encode for Option<T>
where
T: Encode,
{
fn encode(&self, mut write: impl Write) -> Result<()> {
match self {
Some(inner) => {
write.write_u8(1)?;
inner.encode(write)
}
None => Ok(write.write_u8(0)?),
}
}

fn encode_len(&self) -> usize {
match self {
Some(inner) => inner.encode_len() + 1,
None => 1,
}
}
}

impl<T> Decode for Option<T>
where
T: Decode,
{
fn decode(mut read: impl Read) -> Result<Self>
where
Self: Sized,
{
match read.read_u8()? {
0 => Ok(None),
1 => Ok(Some(T::decode(read)?)),
n => Err(Error::InvalidVariantDiscriminant(n as usize, "Option")),
}
}
}

impl<T> Encode for Vec<T>
where
T: Encode,
Expand Down
35 changes: 34 additions & 1 deletion crates/bifrost/src/serialization/nalgebra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use std::io::{Read, Write};

use nalgebra::{Dim, Matrix, Scalar, Storage, StorageMut};
use nalgebra::{
Dim, DimName, Matrix, OPoint, Scalar, Storage, StorageMut, allocator::Allocator,
base::default_allocator::DefaultAllocator,
};

use super::{Decode, Encode};

Expand Down Expand Up @@ -56,6 +59,36 @@ where
}
}

impl<T, D> Encode for OPoint<T, D>
where
T: Scalar + Encode,
D: DimName,
DefaultAllocator: Allocator<D>,
{
fn encode(&self, mut write: impl Write) -> Result<()> {
self.coords.encode(&mut write)
}

fn encode_len(&self) -> usize {
self.coords.encode_len()
}
}

impl<T, D> Decode for OPoint<T, D>
where
T: Scalar + Decode,
D: DimName,
DefaultAllocator: Allocator<D>,
<DefaultAllocator as Allocator<D>>::Buffer<T>: Default,
{
fn decode(mut read: impl Read) -> Result<Self>
where
Self: Sized,
{
Ok(Matrix::into(Matrix::decode(&mut read)?))
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
13 changes: 7 additions & 6 deletions yggdrasil/src/behavior/roles/striker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
localization::RobotPose,
motion::{step_planner::Target, walking_engine::step::Step},
nao::{NaoManager, Priority},
vision::ball_detection::ball_tracker::BallTracker,
vision::ball_detection::{TeamBallPosition, ball_tracker::BallTracker},
};

use std::time::Duration;
Expand Down Expand Up @@ -98,16 +98,18 @@ fn reset_striker_role(mut nao_manager: ResMut<NaoManager>) {
nao_manager.set_right_eye_led(RightEye::fill(color::f32::EMPTY), Priority::default());
}

#[allow(clippy::too_many_arguments)]
pub fn striker_role(
mut commands: Commands,
pose: Res<RobotPose>,
layout_config: Res<LayoutConfig>,
detected_ball_position: Res<TeamBallPosition>,
ball_tracker: Res<BallTracker>,
mut nao_manager: ResMut<NaoManager>,
lost_ball_timer: Option<ResMut<LostBallSearchTimer>>,
time: Res<Time>,
) {
let Some(relative_ball) = ball_tracker.stationary_ball() else {
let Some(relative_ball) = detected_ball_position.0 else {
if let Some(mut timer) = lost_ball_timer {
timer.timer.tick(time.delta()); // <- tick the timer

Expand All @@ -129,8 +131,6 @@ pub fn striker_role(
}
return;
};
let absolute_ball: nalgebra::OPoint<f32, nalgebra::Const<2>> =
pose.robot_to_world(&relative_ball);

if ball_tracker.timestamp.elapsed().as_secs_f32() > 0.5 {
if lost_ball_timer.is_none() {
Expand All @@ -143,6 +143,7 @@ pub fn striker_role(
commands.remove_resource::<LostBallSearchTimer>();
}

let absolute_ball = pose.robot_to_world(&relative_ball);
let ball_angle = pose.angle_to(&absolute_ball);
let ball_distance = relative_ball.coords.norm();
let ball_target: nalgebra::OPoint<f32, nalgebra::Const<3>> =
Expand Down Expand Up @@ -210,12 +211,12 @@ pub fn striker_role(
//TODO: Make this a separate stand-alone behavior
fn set_play(
mut commands: Commands,
ball_tracker: Res<BallTracker>,
detected_ball_position: Res<TeamBallPosition>,
pose: Res<RobotPose>,
behavior_state: Res<State<BehaviorState>>,
walk: Option<Res<Walk>>,
) {
let Some(relative_ball) = ball_tracker.stationary_ball() else {
let Some(relative_ball) = detected_ball_position.0 else {
return;
};
let absolute_ball = pose.robot_to_world(&relative_ball);
Expand Down
2 changes: 2 additions & 0 deletions yggdrasil/src/communication/team.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::time::Duration;

use bevy::prelude::{App, *};
use miette::IntoDiagnostic;
use nalgebra as na;
use tracing::{debug, warn};

use crate::core::config::showtime::ShowtimeConfig;
Expand Down Expand Up @@ -189,6 +190,7 @@ pub enum TeamMessage {
Ping,
Pong,
DetectedWhistle,
DetectedBall(Option<na::Point2<f32>>),
RecognizedRefereePose(RefereePose),
}

Expand Down
121 changes: 121 additions & 0 deletions yggdrasil/src/vision/ball_detection/communication.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use bevy::prelude::*;
use nalgebra::{self as na, Point2};

use crate::{
communication::{TeamCommunication, TeamMessage},
core::debug::DebugContext,
localization::RobotPose,
nao::Cycle,
};

// Import camera proposals
use super::ball_tracker::BallTracker;

// Constant for the minimum acceptable change
const MIN_CHANGE: f32 = 0.1;

pub struct CommunicatedBallsPlugin;

#[derive(Resource, Default, Debug)]
pub struct LastReceivedBall {
pub position: Option<Point2<f32>>,
}

impl Plugin for CommunicatedBallsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<TeamBallPosition>()
.init_resource::<CommunicatedBalls>()
.init_resource::<LastReceivedBall>()
.add_systems(Update, communicate_balls_system);
}
}

#[derive(Resource, Default, Debug)]
pub struct TeamBallPosition(pub Option<Point2<f32>>);

#[derive(Resource, Debug, Default)]
pub struct CommunicatedBalls {
/// For keeping track what position we've sent out.
sent: Option<na::Point2<f32>>,
}

impl CommunicatedBalls {
/// Check it the position has changed enough from last frame.
fn change_enough(&mut self, ball: Option<&na::Point2<f32>>) -> bool {
match (ball, &self.sent) {
(None, None) => false,
(None, Some(_)) => true,
(Some(_), None) => true,
(Some(old), Some(new)) => na::distance(old, new) > MIN_CHANGE,
}
}

/// Send your ball position (even if it's None) as a message.
fn send_message(&mut self, ball_position: Option<na::Point2<f32>>, tc: &mut TeamCommunication) {
tc.outbound_mut()
.update_or_push(TeamMessage::DetectedBall(ball_position))
.expect("Unable to encode detected ball");
self.sent = ball_position;
}

/// Receive messages.
// 2.A.a. If no other robot are detecting a ball, we return the same None we had
// 2.A.b. If there are other robots detecting a ball, we take one from theirs as our own.
fn receive_messages(
comms: &mut TeamCommunication,
pose: &RobotPose,
) -> Option<na::Point2<f32>> {
let mut received_ball = None;

while let Some((_, _, ball)) = comms.inbound_mut().take_map(|_, _, what| match what {
TeamMessage::DetectedBall(ball) => Some(*ball),
_ => None,
}) {
received_ball = received_ball.or(ball);
}
// If we received a ball, transform it from world coordinates to robot coordinates
received_ball.map(|ball| pose.world_to_robot(&ball))
}
}

#[allow(clippy::too_many_arguments)]
fn communicate_balls_system(
mut communicated_balls: ResMut<CommunicatedBalls>,
mut tc: ResMut<TeamCommunication>,
ball_tracker: Res<BallTracker>,
mut team_ball_position: ResMut<TeamBallPosition>,
pose: Res<RobotPose>,
mut last_received: ResMut<LastReceivedBall>,
ctx: DebugContext,
cycle: Res<Cycle>,
) {
let optional_ball_position = ball_tracker.stationary_ball();

// 1. Check if it has changed enough and if so, we send a message.
// let optional_ball_position = ball_position.map(|ball_position| ball_position.0);
if communicated_balls.change_enough(optional_ball_position.as_ref()) {
let transformed_position = optional_ball_position.map(|pos| pose.robot_to_world(&pos));
communicated_balls.send_message(transformed_position, &mut tc);
}

if let Some(new_pos) = CommunicatedBalls::receive_messages(&mut tc, &pose) {
last_received.position = Some(new_pos);
} else {
last_received.position = None;
}

team_ball_position.0 = optional_ball_position.or_else(|| last_received.position);

if let Some(pos) = team_ball_position.0 {
let global = pose.robot_to_world(&pos);
ctx.log_with_cycle(
"/team_ball",
*cycle,
&rerun::Points3D::new([(global.x, global.y, 0.01)])
.with_radii([0.1])
.with_labels(["team_ball"]),
);
} else {
ctx.log_with_cycle("/team_ball", *cycle, &rerun::Clear::recursive());
}
}
5 changes: 5 additions & 0 deletions yggdrasil/src/vision/ball_detection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

pub mod ball_tracker;
pub mod classifier;
mod communication;
pub mod proposal;

use std::{sync::Arc, time::Duration};

pub use ball_tracker::BallHypothesis;
use ball_tracker::BallTracker;
use bevy::prelude::*;
use communication::CommunicatedBallsPlugin;
pub use communication::TeamBallPosition;
use heimdall::{Bottom, CameraLocation, Top};
use nidhogg::types::{FillExt, LeftEye, color};
use proposal::BallProposalConfigs;
Expand All @@ -31,6 +34,7 @@ pub struct BallDetectionPlugin;

impl Plugin for BallDetectionPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(CommunicatedBallsPlugin);
app.init_config::<BallDetectionConfig>();
app.add_plugins((
proposal::BallProposalPlugin::<Top>::default(),
Expand Down Expand Up @@ -144,6 +148,7 @@ fn log_3d_balls(

if let BallHypothesis::Stationary(max_variance) = state {
let pos = robot_pose.robot_to_world(&ball_tracker.state());

if last_logged.is_none_or(|last_logged_cycle| last_ball_tracker_update > last_logged_cycle)
{
*last_logged = Some(last_ball_tracker_update);
Expand Down