Skip to content

Commit

Permalink
feat(hyd): Implementation of nose and body wheel steering for A380 (f…
Browse files Browse the repository at this point in the history
…lybywiresim#8838)




Co-authored-by: alepouna <98479040+alepouna@users.noreply.github.com>
Co-authored-by: Andreas Guther <andreas@guther.family>
Co-authored-by: 2hwk <15316958+2hwk@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 26, 2024
1 parent 51f0662 commit 341b9c3
Show file tree
Hide file tree
Showing 15 changed files with 807 additions and 199 deletions.
48 changes: 16 additions & 32 deletions fbw-a32nx/src/wasm/systems/a320_systems/src/hydraulic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1589,11 +1589,13 @@ impl A320Hydraulic {

nose_steering: SteeringActuator::new(
context,
"NOSE_WHEEL",
Angle::new::<degree>(75.),
AngularVelocity::new::<radian_per_second>(0.35),
Length::new::<meter>(0.075),
Ratio::new::<ratio>(0.18),
Pressure::new::<psi>(2000.),
true,
),

core_hydraulic_updater: MaxStepLoop::new(Self::HYDRAULIC_SIM_TIME_STEP),
Expand Down Expand Up @@ -3537,8 +3539,6 @@ struct A320HydraulicBrakeSteerComputerUnit {
left_brake_pedal_input_id: VariableIdentifier,
right_brake_pedal_input_id: VariableIdentifier,

ground_speed_id: VariableIdentifier,

rudder_pedal_input_id: VariableIdentifier,
tiller_handle_input_id: VariableIdentifier,
tiller_pedal_disconnect_id: VariableIdentifier,
Expand Down Expand Up @@ -3567,8 +3567,6 @@ struct A320HydraulicBrakeSteerComputerUnit {
tiller_steering_limiter: SteeringAngleLimiter<5>,
tiller_input_map: SteeringRatioToAngle<6>,
final_steering_position_request: Angle,

ground_speed: Velocity,
}
impl A320HydraulicBrakeSteerComputerUnit {
const RUDDER_PEDAL_INPUT_GAIN: f64 = 32.;
Expand Down Expand Up @@ -3611,7 +3609,6 @@ impl A320HydraulicBrakeSteerComputerUnit {
right_brake_pedal_input_id: context
.get_identifier("RIGHT_BRAKE_PEDAL_INPUT".to_owned()),

ground_speed_id: context.get_identifier("GPS GROUND SPEED".to_owned()),
rudder_pedal_input_id: context.get_identifier("RUDDER_PEDAL_POSITION_RATIO".to_owned()),
tiller_handle_input_id: context.get_identifier("TILLER_HANDLE_POSITION".to_owned()),
tiller_pedal_disconnect_id: context
Expand Down Expand Up @@ -3656,8 +3653,6 @@ impl A320HydraulicBrakeSteerComputerUnit {
Self::TILLER_INPUT_CURVE_MAP,
),
final_steering_position_request: Angle::new::<degree>(0.),

ground_speed: Velocity::new::<knot>(0.),
}
}

Expand Down Expand Up @@ -3720,7 +3715,7 @@ impl A320HydraulicBrakeSteerComputerUnit {
engine1: &impl Engine,
engine2: &impl Engine,
) {
self.update_steering_demands(lgciu1, engine1, engine2);
self.update_steering_demands(context, lgciu1, engine1, engine2);

self.update_normal_braking_availability(current_pressure.pressure());
self.update_brake_pressure_limitation();
Expand Down Expand Up @@ -3798,6 +3793,7 @@ impl A320HydraulicBrakeSteerComputerUnit {

fn update_steering_demands(
&mut self,
context: &UpdateContext,
lgciu1: &impl LgciuInterface,
engine1: &impl Engine,
engine2: &impl Engine,
Expand All @@ -3815,7 +3811,7 @@ impl A320HydraulicBrakeSteerComputerUnit {

// TODO Here ground speed would be probably computed from wheel sensor logic
let final_steer_rudder_plus_autopilot = self.pedal_steering_limiter.angle_from_speed(
self.ground_speed,
context.ground_speed(),
(steer_angle_from_pedals + steer_angle_from_autopilot)
.min(Angle::new::<degree>(
Self::MAX_RUDDER_INPUT_INCLUDING_AUTOPILOT_DEGREE,
Expand All @@ -3826,7 +3822,7 @@ impl A320HydraulicBrakeSteerComputerUnit {
);

let steer_angle_from_tiller = self.tiller_steering_limiter.angle_from_speed(
self.ground_speed,
context.ground_speed(),
self.tiller_input_map
.angle_demand_from_input_demand(self.tiller_handle_position),
);
Expand Down Expand Up @@ -3877,7 +3873,6 @@ impl SimulationElement for A320HydraulicBrakeSteerComputerUnit {
Ratio::new::<ratio>(reader.read(&self.tiller_handle_input_id));
self.rudder_pedal_position = Ratio::new::<ratio>(reader.read(&self.rudder_pedal_input_id));
self.tiller_pedal_disconnect = reader.read(&self.tiller_pedal_disconnect_id);
self.ground_speed = reader.read(&self.ground_speed_id);

self.autopilot_nosewheel_demand =
Ratio::new::<ratio>(reader.read(&self.autopilot_nosewheel_demand_id));
Expand Down Expand Up @@ -6004,7 +5999,7 @@ mod tests {

use uom::si::{
angle::degree,
angle::radian,
angular_velocity::degree_per_second,
electric_potential::volt,
length::foot,
mass_density::kilogram_per_cubic_meter,
Expand Down Expand Up @@ -6886,8 +6881,9 @@ mod tests {
self
}

fn set_pushback_angle(mut self, angle: Angle) -> Self {
self.write_by_name("PUSHBACK ANGLE", angle.get::<radian>());
fn set_pushback_angle(mut self, angle: AngularVelocity) -> Self {
self.write_by_name("ROTATION VELOCITY BODY Y", angle.get::<degree_per_second>());
self.write_by_name("VELOCITY BODY Z", -1.);
self
}

Expand Down Expand Up @@ -10439,14 +10435,14 @@ mod tests {
.set_tiller_demand(Ratio::new::<ratio>(1.))
.run_waiting_for(Duration::from_secs_f64(5.));

assert!(test_bed.nose_steering_position().get::<degree>() >= 73.9);
assert!(test_bed.nose_steering_position().get::<degree>() >= 73.5);
assert!(test_bed.nose_steering_position().get::<degree>() <= 75.1);

test_bed = test_bed
.set_tiller_demand(Ratio::new::<ratio>(-1.))
.run_waiting_for(Duration::from_secs_f64(10.));

assert!(test_bed.nose_steering_position().get::<degree>() <= -73.9);
assert!(test_bed.nose_steering_position().get::<degree>() <= -73.5);
assert!(test_bed.nose_steering_position().get::<degree>() >= -75.1);
}

Expand Down Expand Up @@ -10999,29 +10995,17 @@ mod tests {

test_bed = test_bed
.set_pushback_state(true)
.set_pushback_angle(Angle::new::<degree>(80.))
.run_waiting_for(Duration::from_secs_f64(0.5));

// Do not turn instantly in 0.5s
assert!(
test_bed.get_nose_steering_ratio() > Ratio::new::<ratio>(0.)
&& test_bed.get_nose_steering_ratio() < Ratio::new::<ratio>(0.5)
);

test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.));
.set_pushback_angle(AngularVelocity::new::<degree_per_second>(-5.))
.run_waiting_for(Duration::from_secs_f64(5.));

// Has turned fully after 5s
assert!(test_bed.get_nose_steering_ratio() > Ratio::new::<ratio>(0.9));

// Going left
test_bed = test_bed
.set_pushback_state(true)
.set_pushback_angle(Angle::new::<degree>(-80.))
.run_waiting_for(Duration::from_secs_f64(0.5));

assert!(test_bed.get_nose_steering_ratio() > Ratio::new::<ratio>(0.2));

test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(5.));
.set_pushback_angle(AngularVelocity::new::<degree_per_second>(5.))
.run_waiting_for(Duration::from_secs_f64(5.));

// Has turned fully left after 5s
assert!(test_bed.get_nose_steering_ratio() < Ratio::new::<ratio>(-0.9));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@
<UseTemplate Name="ASOBO_GT_Anim_Code">
<NODE_ID>left_bws</NODE_ID>
<ANIM_NAME>left_bws</ANIM_NAME>
<ANIM_CODE>(L:A32NX_BODY_WHEEL_STEERING_POSITION)</ANIM_CODE>
<ANIM_CODE>(L:A32NX_LEFT_BODY_WHEEL_STEERING_POSITION)</ANIM_CODE>
<ANIM_LENGTH>1</ANIM_LENGTH>
</UseTemplate>
<!-- rear right Steering -->
<UseTemplate Name="ASOBO_GT_Anim_Code">
<NODE_ID>right_bws</NODE_ID>
<ANIM_NAME>right_bws</ANIM_NAME>
<ANIM_CODE>(L:A32NX_BODY_WHEEL_STEERING_POSITION)</ANIM_CODE>
<ANIM_CODE>(L:A32NX_RIGHT_BODY_WHEEL_STEERING_POSITION)</ANIM_CODE>
<ANIM_LENGTH>1</ANIM_LENGTH>
</UseTemplate>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,6 @@ pub struct RefuelPanelInput {
refuel_rate_setting: RefuelRate,
refuel_rate_setting_id: VariableIdentifier,

ground_speed_id: VariableIdentifier,
ground_speed: Velocity,

is_on_ground_id: VariableIdentifier,
is_on_ground: bool,

engine_state_ids: [VariableIdentifier; 4],
engine_states: [EngineState; 4],

Expand All @@ -93,10 +87,6 @@ impl RefuelPanelInput {

refuel_rate_setting_id: context.get_identifier("EFB_REFUEL_RATE_SETTING".to_owned()),
refuel_rate_setting: RefuelRate::Real,
ground_speed_id: context.get_identifier("GPS GROUND SPEED".to_owned()),
ground_speed: Velocity::default(),
is_on_ground_id: context.get_identifier("SIM ON GROUND".to_owned()),
is_on_ground: false,

engine_state_ids: [1, 2, 3, 4]
.map(|id| context.get_identifier(format!("ENGINE_STATE:{id}"))),
Expand Down Expand Up @@ -131,15 +121,15 @@ impl RefuelPanelInput {
self.refuel_rate_setting
}

fn refuel_is_enabled(&self) -> bool {
fn refuel_is_enabled(&self, context: &UpdateContext) -> bool {
// TODO: Should this be if two engines are running instead of just one?
self.refuel_status
&& self
.engine_states
.iter()
.all(|state| *state == EngineState::Off)
&& self.is_on_ground
&& self.ground_speed < Velocity::new::<knot>(0.1)
&& context.is_on_ground()
&& context.ground_speed() < Velocity::new::<knot>(0.1)
}

fn target_zero_fuel_weight(&self) -> Mass {
Expand All @@ -156,8 +146,7 @@ impl SimulationElement for RefuelPanelInput {
Mass::new::<kilogram>(reader.read(&self.total_desired_fuel_id));
self.refuel_status = reader.read(&self.refuel_status_id);
self.refuel_rate_setting = reader.read(&self.refuel_rate_setting_id);
self.ground_speed = reader.read(&self.ground_speed_id);
self.is_on_ground = reader.read(&self.is_on_ground_id);

for (id, state) in self
.engine_state_ids
.iter()
Expand Down Expand Up @@ -216,8 +205,8 @@ impl IntegratedRefuelPanel {
self.input.refuel_rate()
}

fn refuel_is_enabled(&self) -> bool {
self.input.refuel_is_enabled()
fn refuel_is_enabled(&self, context: &UpdateContext) -> bool {
self.input.refuel_is_enabled(context)
}

fn target_zero_fuel_weight(&self) -> Mass {
Expand Down Expand Up @@ -267,14 +256,13 @@ impl RefuelApplication {
refuel_panel_input.target_zero_fuel_weight_cg_mac(),
);

// TODO: Uncomment when IS_SIM_READY variable is implemented
// if !context.is_sim_ready() {
// refuel_panel_input.set_fuel_desired(fuel_system.total_load());
//}
if !context.is_sim_ready() {
refuel_panel_input.set_fuel_desired(fuel_system.total_load());
}

match refuel_panel_input.refuel_rate() {
RefuelRate::Real => {
if refuel_panel_input.refuel_is_enabled() {
if refuel_panel_input.refuel_is_enabled(context) {
self.refuel_driver.execute_timed_refuel(
context.delta(),
false,
Expand All @@ -285,7 +273,7 @@ impl RefuelApplication {
}
}
RefuelRate::Fast => {
if refuel_panel_input.refuel_is_enabled() {
if refuel_panel_input.refuel_is_enabled(context) {
self.refuel_driver.execute_timed_refuel(
context.delta(),
true,
Expand Down
Loading

0 comments on commit 341b9c3

Please sign in to comment.