Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 25 additions & 1 deletion .github/workflows/gazebo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,32 @@ jobs:
./scripts/compose-cascade.rs dist/falcon-components-v1.137 /tmp/composed.wasm
cd tests/cascade-sitl-wasm
cargo build --release --features gazebo
# TWO DIFFERENT FAILURES, treated differently on purpose.
#
# INFRASTRUCTURE broken (cannot build, cannot connect to gz, cannot
# instantiate the component) => FAIL. That is this gate's job and a
# regression in it must stop a merge.
#
# The FLIGHT OUTCOME => recorded, not fatal. The published cascade
# does not currently take off in gz: it wraps the legacy PID stages
# and has no arming or hover-thrust bias, which CascadePartition
# supplies (#388). That is a known, tracked gap, not something this
# or any unrelated PR introduces, and blocking every merge on it
# would only get the leg deleted.
#
# "LOOP CLOSES" is the discriminator: the harness prints it only
# after the component has actually executed its ticks. Absent =>
# infrastructure. Present with a hold error => measurement.
set +e
BACKEND=gazebo ./target/release/cascade-sitl-wasm /tmp/composed.wasm 2500 0.004 \
| tee /tmp/wasm-gz.txt
2>&1 | tee /tmp/wasm-gz.txt
set -e
if ! grep -q 'LOOP CLOSES' /tmp/wasm-gz.txt; then
echo "::error::the wasm cascade did not execute in gz — infrastructure failure, not a flight outcome"
exit 1
fi
HOLD=$(sed -n 's/^HOLD ERROR *: .*|err| = \([0-9.]*\) m.*/\1/p' /tmp/wasm-gz.txt | tail -1)
echo "::notice::wasm cascade ran ${HOLD:-?} m hold error in gz — tracked in #388 until the components wrap the flown stack"

# ── Record the flight ────────────────────────────────────────────────
# Every gz run leaves footage. The bench has recorded video for releases
Expand Down
18 changes: 14 additions & 4 deletions tests/cascade-sitl-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,23 @@ publish = false
[workspace]

[features]
# Declared only so the `#[cfg(feature = "gazebo")]` blocks inside the included
# physics.rs are a known-false cfg rather than an unexpected one. Never enabled
# here: this harness flies MockPhysics, not the gz bridge.
gazebo = []
# Real gz bridge, same shape as examples/falcon-sitl-gz: the included physics.rs
# gates its `gz_real` module on this, so enabling it brings in the same
# GazeboPhysics the native bench flies. Needs the gz-transport13 dev libs on
# PKG_CONFIG_PATH and a running `gz sim`. Off by default so the mock path stays
# dependency-free.
gazebo = ["dep:gz-transport", "dep:gz-msgs", "dep:crossbeam-channel"]

[dependencies]
wasmtime = { version = "41", features = ["component-model"] }
anyhow = "1"
libm = "0.2"
relay-ekf = { path = "../../crates/relay-ekf" }

# Optional — only with feature `gazebo`. Versions match examples/falcon-sitl-gz
# EXACTLY; a different gz-transport here would be a different bridge than the
# one every native SITL result was produced with, and the comparison would stop
# meaning anything.
gz-transport = { version = "0.10", features = ["harmonic"], optional = true }
gz-msgs = { version = "0.10", features = ["harmonic"], optional = true }
crossbeam-channel = { version = "0.5", optional = true }
29 changes: 27 additions & 2 deletions tests/cascade-sitl-wasm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ mod physics;

use anyhow::{bail, Context, Result};
use physics::{MockPhysics, Physics};
#[cfg(feature = "gazebo")]
use physics::GazeboPhysics;
use wasmtime::component::{Component, Linker};
use wasmtime::{Config, Engine, Store};

Expand Down Expand Up @@ -80,11 +82,34 @@ fn main() -> Result<()> {
let down: f32 = std::env::var("TARGET_DOWN").ok().and_then(|v| v.parse().ok()).unwrap_or(-2.0);
let target = Waypoint { north: 0.0, east: 0.0, down, yaw: 0.0 };
let noise: f32 = std::env::var("IMU_NOISE").ok().and_then(|v| v.parse().ok()).unwrap_or(0.0);
let mut plant = MockPhysics::at_rest();
// BACKEND. `mock` is the analytic plant every number in #380 came from;
// `gazebo` is the same real bridge the native bench flies, so a wasm result
// and a native result are comparable rather than merely adjacent.
let backend = std::env::var("BACKEND").unwrap_or_else(|_| "mock".into());
let mut boxed: Box<dyn Physics> = match backend.as_str() {
"mock" => Box::new(MockPhysics::at_rest()),
#[cfg(feature = "gazebo")]
"gazebo" => {
let world = std::env::var("GZ_WORLD").unwrap_or_else(|_| "falcon".into());
let model = std::env::var("GZ_MODEL").unwrap_or_else(|_| "quad".into());
match GazeboPhysics::connect(&world, &model) {
Some(p) => Box::new(p),
None => bail!(
"could not connect to gz world '{world}' model '{model}'. Is `gz sim` \
running with worlds/falcon-quad.sdf, and gz-transport13 on the \
library path?"
),
}
}
#[cfg(not(feature = "gazebo"))]
"gazebo" => bail!("rebuild with --features gazebo to use the real gz bridge"),
other => bail!("unknown BACKEND '{other}' (expected: mock | gazebo)"),
};
let plant = &mut *boxed;

println!("=== wasm cascade in the SITL loop ===");
println!("component : {wasm}");
println!("plant : MockPhysics (the same module falcon-sitl-gz flies)");
println!("plant : {} (BACKEND={backend})", plant.name());
println!("target : hold N=0 E=0 D={down} m, yaw 0");
println!("schedule : {ticks} ticks @ dt={dt}s ({:.1}s, {:.0} Hz)", ticks as f32 * dt, 1.0 / dt);
println!();
Expand Down
Loading