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
11 changes: 6 additions & 5 deletions .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,12 @@ jobs:
working-directory: relay
env:
CARGO_BAZEL_REPIN: "1"
# Also builds the P3 (Component-Model Preview 3) async STREAM components
# for the full cFS set: //:relay-lc-stream + //:relay-sch-stream +
# //:relay-hk-stream + //:relay-sc-stream (#STREAM-P01..P04), so their P3
# async build is CI-validated, not just asserted.
run: bazel build //:falcon-cascade-composed //:relay-lc-stream //:relay-sch-stream //:relay-hk-stream //:relay-sc-stream //:relay-lc //:relay-sch //:relay-hk //:relay-sc
# Also builds the P3 (Component-Model Preview 3) async STREAM components:
# the full cFS set //:relay-lc-stream + //:relay-sch-stream +
# //:relay-hk-stream + //:relay-sc-stream (#STREAM-P01..P04), plus the
# first control-cascade stream //:falcon-rate-stream (#STREAM-P05), so
# their P3 async build is CI-validated, not just asserted.
run: bazel build //:falcon-cascade-composed //:relay-lc-stream //:relay-sch-stream //:relay-hk-stream //:relay-sc-stream //:falcon-rate-stream //:relay-lc //:relay-sch //:relay-hk //:relay-sc

# Exercise the witness MC/DC path end-to-end (#145): forces the
# harness_crates crate_universe (wasmtime/tokio) to resolve, builds the
Expand Down
20 changes: 20 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,26 @@ rust_wasm_component_bindgen(
profiles = ["release"],
)

# P3 STREAM variant — stream<rate-input> -> stream<torque-setpoint> (#168).
# The first control-cascade engine as a P3 async stream, built the proven
# cFS-stream way (rust_wasm_component_bindgen, wasi p3) over the verified
# :relay-rate library; self-contained WIT (does not touch cascade.wit).
wit_library(
name = "falcon_rate_stream_wit",
srcs = ["wit/falcon-rate-stream/rate_stream.wit"],
package_name = "falcon:rate-stream",
world = "rate-stream-component",
)

rust_wasm_component_bindgen(
name = "falcon-rate-stream",
srcs = ["wasm/cm/rate/src/stream.rs"],
wit = ":falcon_rate_stream_wit",
wasi_version = "p3",
deps = ["@crates//:wit-bindgen", ":relay-rate"],
profiles = ["release"],
)

rust_wasm_component_bindgen(
name = "falcon-mixer",
crate_features = ["bazel-bindings"],
Expand Down
43 changes: 43 additions & 0 deletions artifacts/swreq/SWREQ-RELAY-STREAM-P05.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
artifacts:
- id: SWREQ-RELAY-STREAM-P05
type: sw-req
title: "STREAM-P05 — falcon rate controller exposes a Component-Model Preview 3 async stream interface (first control-cascade engine)"
status: implemented
description: >
Extends the streamification template from the cFS engines (LC/SCH/HK/SC,
P01-P04) to the CONTROL CASCADE: the falcon rate controller shall expose a
WASM Component-Model Preview 3 (P3) async interface
monitor: func(inputs: stream<rate-input>) -> stream<torque-setpoint>
implemented as a genuine async stream transformer (async fn over a
StreamReader, .next().await on the inputs, .write().await on the torque
outputs), built as a P3 component (wasi_version = "p3"). This is the first
flight-control engine as a P3 stream, toward a composed flight-control
STREAM pipeline (iekf -> pos -> att -> rate -> mixer).

It RUNS THE VERIFIED CONTROLLER, not an inline copy: the component does
`use relay_rate::{RatePid, Timestamp};` and the //:falcon-rate-stream
bindgen target `deps` on the :relay-rate Bazel library (the verified
body-rate PID), so the streaming monitor() drives the ACTUAL relay-rate
RatePid::tick. The WIT is SELF-CONTAINED (its own falcon:rate-stream
package) and does not modify the shared cascade.wit.

As with P01-P04, the async RUNTIME that executes these streams is kiln's
fuel-metered async scheduler (shipped but dormant); this requirement's
scope is AUTHORING + BUILDING a valid P3 async component, NOT end-to-end
execution on kiln.
tags: [relay, falcon-rate, cascade, stream, async, p3, component-model, streamification]
fields:
req-type: functional
priority: must
verification-criteria: >
wit/falcon-rate-stream/rate_stream.wit declares
`monitor: func(stream<rate-input>) -> stream<torque-setpoint>`; the Rust
implementation (wasm/cm/rate/src/stream.rs) is a real async stream
transformer over StreamReader with .next().await / writer.write().await,
driving relay_rate::RatePid::tick; the //:falcon-rate-stream bazel target
builds it as a wasi_version="p3" component, exercised in CI
(.github/workflows/bazel.yml builds it alongside the cFS stream
components).
links:
- type: derives-from
target: SYSREQ-FALCON-008
44 changes: 44 additions & 0 deletions artifacts/verification/FV-RELAY-STREAM-005.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
artifacts:
- id: FV-RELAY-STREAM-005
type: sw-verification
title: "falcon rate controller P3 async stream component — runs the verified PID, builds as Component-Model P3 (v1.66)"
status: implemented
description: >
Verifies SWREQ-RELAY-STREAM-P05 for the rate controller — the first
control-cascade engine promoted to a real WASM Component-Model Preview 3
(P3) async stream transformer, extending the streamification template
(FV-RELAY-STREAM-001..004) from the cFS engines to the flight cascade.

Evidence:
1. The WIT (wit/falcon-rate-stream/rate_stream.wit) declares genuine P3
stream types: `monitor: func(inputs: stream<rate-input>) ->
stream<torque-setpoint>`. Self-contained (falcon:rate-stream package),
not touching the shared cascade.wit.
2. The implementation (wasm/cm/rate/src/stream.rs) is a real async stream
transformer: `async fn monitor(mut inputs: StreamReader<RateInput>) ->
StreamReader<TorqueSetpoint>` with `wit_stream::new()`, `while let
Some(inp) = inputs.next().await`, and `writer.write(...).await`.
3. RUNS THE VERIFIED CONTROLLER: `use relay_rate::{RatePid, Timestamp};`
and //:falcon-rate-stream `deps` on :relay-rate, so the streaming
monitor() drives the ACTUAL relay-rate RatePid::tick (the body-rate
PID) — no inline copy. The PID integrator/derivative state persists
across the stream (static RatePid), timestamp a synthesised 1 kHz
counter, exactly as the sync falcon-rate component.
4. The mechanical ORACLE: the //:falcon-rate-stream bazel target
(wasi_version = "p3") builds it as a P3 component, EXERCISED in CI
(.github/workflows/bazel.yml builds it alongside the four cFS stream
components), so "valid P3 async component driving the verified PID" is
CI-validated, not asserted.

Honest scope (same as STREAM-001..004): verifies AUTHORED + BUILDS as P3
async driving the verified controller; does NOT verify end-to-end
execution on kiln's async scheduler (shipped but dormant). Composing the
cascade stream pipeline (iekf->pos->att->rate->mixer) is the follow-on.
tags: [verification, relay, falcon-rate, cascade, stream, async, p3, component-model, "v1.66"]
fields:
method: automated-test
steps:
- run: bazel build //:falcon-rate-stream
links:
- type: verifies
target: SWREQ-RELAY-STREAM-P05
69 changes: 69 additions & 0 deletions wasm/cm/rate/src/stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Falcon Rate Controller — P3 Stream Transformer WASM component.
//
// Takes stream<rate-input>, emits stream<torque-setpoint>.
// Wraps the verified relay-rate body-rate PID (RatePid) — the first
// control-cascade engine promoted to a P3 async stream (#168 cascade arc).
//
// Built as a rust_wasm_component_bindgen (wasi_version="p3"), the same proven
// pattern as the cFS stream components — deps on the verified :relay-rate
// Bazel library, NOT an inline copy of the controller.

use relay_rate::{RatePid, Timestamp};

use falcon_rate_stream_bindings::exports::falcon::rate_stream::rate_stream::{
Guest, RateInput as WitInput, TorqueSetpoint as WitTorque,
};

struct Component;

// Stateful across the stream: the PID integrator/derivative state persists,
// and the timestamp is a synthesised 1 kHz counter (as in the sync component).
static mut PID: Option<RatePid> = None;
static mut TICK_MS: u64 = 0;

fn pid() -> &'static mut RatePid {
unsafe {
if PID.is_none() { PID = Some(RatePid::new()); }
PID.as_mut().unwrap()
}
}

fn next_timestamp() -> Timestamp {
unsafe {
let ms = TICK_MS;
TICK_MS += 1;
Timestamp {
seconds: ms / 1000,
fraction: ((ms % 1000) * (1u64 << 32) / 1000) as u32,
}
}
}

impl Guest for Component {
/// STREAM TRANSFORMER: reads rate-loop inputs, steps the verified PID on
/// each, writes the torque setpoint to the output stream.
async fn monitor(
mut inputs: wit_bindgen::rt::async_support::StreamReader<WitInput>,
) -> wit_bindgen::rt::async_support::StreamReader<WitTorque> {
let (mut writer, reader) = falcon_rate_stream_bindings::wit_stream::new::<WitTorque>();

while let Some(inp) = inputs.next().await {
let torque = pid().tick(
next_timestamp(),
[inp.wx, inp.wy, inp.wz],
[inp.rx, inp.ry, inp.rz],
);
let out = WitTorque {
tx: torque[0],
ty: torque[1],
tz: torque[2],
thrust: inp.thrust, // thrust passes straight through the rate loop
};
let _ = writer.write(vec![out]).await;
}

reader
}
}

falcon_rate_stream_bindings::export!(Component with_types_in falcon_rate_stream_bindings);
39 changes: 39 additions & 0 deletions wit/falcon-rate-stream/rate_stream.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package falcon:rate-stream@0.1.0;

/// Rate controller — stream transformer edition.
///
/// Takes a stream of rate-loop inputs (measured body rates + body-rate
/// setpoint), emits a stream of torque setpoints. Wraps the verified
/// relay-rate body-rate PID (RatePid). Self-contained types (does not depend
/// on the shared cascade.wit) — the first control-cascade engine as a P3 async
/// stream (#168 cascade arc).
interface rate-stream {
/// Rate-loop input: measured body rates (gyro, rad/s) + the body-rate
/// setpoint (rad/s) + collective thrust (passed straight through).
record rate-input {
wx: f32,
wy: f32,
wz: f32,
rx: f32,
ry: f32,
rz: f32,
thrust: f32,
}

/// Body-frame torque + collective thrust.
record torque-setpoint {
tx: f32,
ty: f32,
tz: f32,
thrust: f32,
}

/// STREAM TRANSFORMER: rate-loop inputs in → torque setpoints out.
/// Each input drives the stateful PID one step; the resulting torque is
/// emitted to the output stream.
monitor: func(inputs: stream<rate-input>) -> stream<torque-setpoint>;
}

world rate-stream-component {
export rate-stream;
}
Loading