Skip to content

Commit 6c0245f

Browse files
Fix robot legs when picked up + more contacts (#431)
Co-authored-by: Gijs de Jong <berichtaangijs@gmail.com>
1 parent cda12fc commit 6c0245f

7 files changed

Lines changed: 126 additions & 29 deletions

File tree

deploy/config/yggdrasil.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ held_duration_threshold = 500
66

77
[filter.fsr]
88
# Threshold for ground contact detection using average FSR sensor values from both feet.
9-
ground_contact_threshold = 0.01
9+
ground_contact_threshold = 0.12
10+
# Timeout for changing the value of the ground contact state, in milliseconds.
11+
ground_contact_timeout = 20
1012
# Maximum amount of pressure measured by a single sensor.
1113
max_pressure = 5.0
1214
# Initial value for minimum pressure measured by a single sensor.

yggdrasil/src/behavior/behaviors/sitting.rs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use bevy::prelude::*;
22

3-
use nidhogg::types::{color, FillExt, RightEye};
3+
use crate::motion::keyframe::KeyframeExecutor;
4+
use crate::sensor::fsr::Contacts;
5+
use nidhogg::types::{color, FillExt, LegJoints, RightEye};
6+
use nidhogg::NaoState;
47

58
use crate::{
69
behavior::engine::{in_behavior, Behavior, BehaviorState},
@@ -21,23 +24,55 @@ impl Plugin for SittingBehaviorPlugin {
2124

2225
/// This is often the starting behavior of the robot.
2326
/// In this state the robot sits down, after which it unstiffens its legs, arms and head.
24-
#[derive(Resource)]
25-
pub struct Sitting;
27+
#[derive(Resource, Default)]
28+
pub struct Sitting {
29+
/// Stores the initial leg position when ground contact is lost.
30+
locked_leg_position: Option<LegJoints<f32>>,
31+
}
2632

2733
impl Behavior for Sitting {
2834
const STATE: BehaviorState = BehaviorState::Sitting;
2935
}
3036

31-
pub fn sitting(mut walking_engine: ResMut<WalkingEngine>, mut nao_manager: ResMut<NaoManager>) {
37+
pub fn sitting(
38+
mut sitting: ResMut<Sitting>,
39+
mut walking_engine: ResMut<WalkingEngine>,
40+
mut nao_manager: ResMut<NaoManager>,
41+
nao_state: Res<NaoState>,
42+
contacts: Res<Contacts>,
43+
keyframe_executor: Res<KeyframeExecutor>,
44+
) {
3245
// Makes right eye blue.
3346
nao_manager.set_right_eye_led(RightEye::fill(color::f32::BLUE), Priority::default());
3447

35-
if walking_engine.is_sitting() {
36-
// Makes robot floppy except for hip joints, locked in sitting position.
37-
nao_manager.unstiff_sit(UNSTIFF_PRIORITY);
38-
} else {
48+
if !walking_engine.is_sitting() {
3949
walking_engine.request_sit();
50+
nao_manager.unstiff_arms(UNSTIFF_PRIORITY);
51+
return;
52+
}
53+
54+
// When ground contact is lost, set the current position in air.
55+
if !contacts.ground && !keyframe_executor.is_motion_active() {
56+
if sitting.locked_leg_position.is_none() {
57+
sitting.locked_leg_position = Some(capture_leg_position(&nao_state));
58+
}
59+
if let Some(leg_positions) = sitting.locked_leg_position.as_ref() {
60+
nao_manager.stiff_sit(leg_positions.clone(), Priority::High);
61+
}
62+
63+
// Resets locked position and makes robot floppy except for hip joints in sitting position.
64+
} else {
65+
sitting.locked_leg_position = None;
66+
nao_manager.unstiff_sit(UNSTIFF_PRIORITY);
67+
nao_manager.unstiff_arms(UNSTIFF_PRIORITY);
4068
}
69+
}
70+
71+
fn capture_leg_position(nao_state: &NaoState) -> LegJoints<f32> {
72+
let position = nao_state.position.clone();
4173

42-
nao_manager.unstiff_arms(UNSTIFF_PRIORITY);
74+
LegJoints::builder()
75+
.left_leg(position.left_leg_joints())
76+
.right_leg(position.right_leg_joints())
77+
.build()
4378
}

yggdrasil/src/behavior/engine.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use super::{
77
WalkBehaviorPlugin, WalkToBehaviorPlugin, WalkToSetBehaviorPlugin,
88
},
99
roles::{
10-
Defender, DefenderRolePlugin, Goalkeeper, GoalkeeperRolePlugin, InstinctRolePlugin,
10+
DefenderRolePlugin, Goalkeeper, GoalkeeperRolePlugin, Instinct, InstinctRolePlugin,
1111
Striker, StrikerRolePlugin,
1212
},
1313
};
@@ -100,7 +100,7 @@ impl Role {
100100
match player_number {
101101
1 => commands.set_role(Goalkeeper),
102102
5 => commands.set_role(Striker::WalkToBall),
103-
_ => commands.set_role(Defender),
103+
_ => commands.set_role(Instinct),
104104
}
105105
}
106106

yggdrasil/src/behavior/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ pub mod primary_state;
55
pub mod roles;
66

77
use bevy::{app::PluginGroupBuilder, prelude::*};
8-
use engine::BehaviorEnginePlugin;
98

109
pub use behavior_config::BehaviorConfig;
1110

@@ -15,7 +14,7 @@ pub struct BehaviorPlugins;
1514
impl PluginGroup for BehaviorPlugins {
1615
fn build(self) -> PluginGroupBuilder {
1716
PluginGroupBuilder::start::<Self>()
18-
.add(BehaviorEnginePlugin)
17+
.add(engine::BehaviorEnginePlugin)
1918
.add(primary_state::PrimaryStatePlugin)
2019
}
2120
}

yggdrasil/src/behavior/roles/instinct.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use nalgebra::Point2;
66
use crate::{
77
behavior::{
88
behaviors::{CatchFall, Sitting, Stand, StandLookAt, Standup, WalkToSet},
9-
engine::CommandsBehaviorExt,
9+
engine::{in_role, CommandsBehaviorExt},
1010
primary_state::{update_primary_state, PrimaryState},
1111
roles::Striker,
1212
},
@@ -25,7 +25,9 @@ impl Plugin for InstinctRolePlugin {
2525
fn build(&self, app: &mut App) {
2626
app.add_systems(
2727
Update,
28-
behavior.after(update_primary_state), // .run_if(resource_exists::<Instinct>),
28+
behavior
29+
.after(update_primary_state)
30+
.run_if(in_role::<Instinct>),
2931
);
3032
}
3133
}
@@ -55,7 +57,7 @@ pub fn behavior(
5557

5658
if behavior == &BehaviorState::StartUp {
5759
if walking_engine.is_sitting() || head_buttons.all_pressed() {
58-
commands.set_behavior(Sitting);
60+
commands.set_behavior(Sitting::default());
5961
}
6062
if *primary_state == PrimaryState::Initial {
6163
commands.set_behavior(Stand);
@@ -65,7 +67,9 @@ pub fn behavior(
6567

6668
// unstiff has the number 1 precedence
6769
if *primary_state == PrimaryState::Sitting {
68-
commands.set_behavior(Sitting);
70+
if !matches!(behavior, BehaviorState::Sitting) {
71+
commands.set_behavior(Sitting::default());
72+
}
6973
return;
7074
}
7175

@@ -109,7 +113,7 @@ pub fn behavior(
109113
let ball_or_origin = most_confident_ball.unwrap_or(Point2::origin());
110114

111115
match *primary_state {
112-
PrimaryState::Sitting => commands.set_behavior(Sitting),
116+
PrimaryState::Sitting => commands.set_behavior(Sitting::default()),
113117
PrimaryState::Standby
114118
| PrimaryState::Penalized
115119
| PrimaryState::Finished

yggdrasil/src/nao/manager.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,11 @@ impl NaoManager {
480480
self.set_legs(leg_positions, leg_stiffness, priority)
481481
}
482482

483+
pub fn stiff_sit(&mut self, leg_positions: LegJoints<f32>, priority: Priority) -> &mut Self {
484+
let leg_stiffness = LegJoints::fill(0.3);
485+
self.set_legs(leg_positions, leg_stiffness, priority)
486+
}
487+
483488
/// Disable all motors in the arms.
484489
pub fn unstiff_arms(&mut self, priority: Priority) -> &mut Self {
485490
self.set_arms(

yggdrasil/src/sensor/fsr.rs

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
use std::time::{Duration, Instant};
22

33
use super::SensorConfig;
4-
use crate::{motion::walk::SwingFootSwitchedEvent, prelude::*};
4+
use crate::prelude::*;
5+
use crate::sensor::low_pass_filter::LowPassFilter;
56
use bevy::prelude::*;
7+
use nalgebra::SVector;
8+
9+
use crate::motion::walk::SwingFootSwitchedEvent;
610
use nidhogg::{
711
types::{FillExt, Fsr, FsrFoot},
812
NaoState,
913
};
1014
use serde::{Deserialize, Serialize};
1115
use serde_with::{serde_as, DurationMilliSeconds};
1216

17+
// Omega for the low-pass filter.
18+
const OMEGA: f32 = 0.7;
19+
1320
/// Plugin offering the Force Sensitive Resistor (FSR) sensor data of the Nao,
1421
/// derived from the raw [`NaoState`].
1522
pub struct FSRSensorPlugin;
@@ -43,13 +50,21 @@ impl Plugin for FSRSensorPlugin {
4350
pub struct FsrConfig {
4451
/// Threshold for ground contact detection using average FSR sensor values from both feet.
4552
pub ground_contact_threshold: f32,
53+
54+
/// Timeout for change of value of the ground contact state in milliseconds.
55+
#[serde_as(as = "DurationMilliSeconds")]
56+
pub ground_contact_timeout: Duration,
57+
4658
/// Maximum amount of pressure measured by a single sensor.
4759
pub max_pressure: f32,
60+
4861
/// Initial value for minimum pressure measured by a single sensor.
4962
pub min_pressure: f32,
63+
5064
/// The time to sample pressure values before updating the maximum value for each sensor, in milliseconds.
5165
#[serde_as(as = "DurationMilliSeconds")]
5266
pub highest_pressure_update_rate: Duration,
67+
5368
/// The number of foot switches required before updating the minimum value for each sensor.
5469
pub num_foot_switches: u32,
5570
}
@@ -67,30 +82,67 @@ impl FsrConfig {
6782
}
6883

6984
/// Struct containing the various contact points of the Nao.
70-
#[derive(Resource, Default)]
85+
#[derive(Resource)]
7186
pub struct Contacts {
7287
/// Whether the robot has ground contact.
7388
pub ground: bool,
89+
7490
/// Whether the robot's left foot has ground contact.
7591
pub left_foot: bool,
92+
7693
/// Whether the robot's right foot has ground contact.
7794
pub right_foot: bool,
95+
96+
/// Timestamp of detected change in pressure state.
97+
pub last_switched: Instant,
98+
99+
/// The first-order Butterworth low-pass filter to apply to the FSR sensor data.
100+
pub lpf: LowPassFilter<1>,
78101
}
79102

80-
pub fn force_sensitive_resistor_sensor(
81-
nao_state: Res<NaoState>,
82-
mut force_sensitive_resistors: ResMut<Fsr>,
83-
) {
84-
force_sensitive_resistors.left_foot = nao_state.fsr.left_foot.clone();
85-
force_sensitive_resistors.right_foot = nao_state.fsr.right_foot.clone();
103+
impl Default for Contacts {
104+
fn default() -> Self {
105+
Contacts {
106+
ground: true,
107+
left_foot: false,
108+
right_foot: false,
109+
last_switched: Instant::now(),
110+
lpf: LowPassFilter::new(OMEGA),
111+
}
112+
}
113+
}
114+
115+
pub fn force_sensitive_resistor_sensor(nao_state: Res<NaoState>, mut fsr: ResMut<Fsr>) {
116+
fsr.left_foot = nao_state.fsr.left_foot.clone();
117+
fsr.right_foot = nao_state.fsr.right_foot.clone();
86118
}
87119

88-
fn update_contacts(config: Res<SensorConfig>, fsr: Res<Fsr>, mut contacts: ResMut<Contacts>) {
120+
fn update_contacts(
121+
config: Res<SensorConfig>,
122+
fsr: Res<Fsr>,
123+
mut contacts: ResMut<Contacts>,
124+
mut last_pressure: Local<bool>,
125+
) {
89126
let config = &config.fsr;
90127

91-
contacts.ground = fsr.avg() >= config.ground_contact_threshold;
92128
contacts.left_foot = fsr.left_foot.sum() >= config.min_pressure;
93129
contacts.right_foot = fsr.right_foot.sum() >= config.min_pressure;
130+
131+
// Retrieve FSR values and apply low-pass filter.
132+
let fsr_vector = SVector::<f32, 1>::from([fsr.avg()]);
133+
let filtered_fsr = contacts.lpf.update(fsr_vector);
134+
let current_pressure = filtered_fsr.x > config.ground_contact_threshold;
135+
136+
if current_pressure != *last_pressure {
137+
contacts.last_switched = Instant::now();
138+
}
139+
140+
// Only update the ground state if timeout duration has elapsed.
141+
if contacts.last_switched.elapsed() >= config.ground_contact_timeout {
142+
contacts.ground = current_pressure;
143+
}
144+
145+
*last_pressure = current_pressure;
94146
}
95147

96148
#[derive(Debug, Clone, Default)]

0 commit comments

Comments
 (0)