Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Affecting Change in the tests

For our next test, we want to raise the system load and then see how that affects temperature (it should rise).

We don't currently have a way to tell the simulation to raise the load. But in interactive mode we can, and we did that by sending InteractionEvent messages. Let's do that here. We'll need to pass in the InteractionChannelWrapper we need for sending these messages into the integration_test() function.

Start by adding these imports:

#![allow(unused)]
fn main() {
    use crate::entry::InteractionChannelWrapper;
    use ec_common::espi_service::EventChannel;
    use crate::events::InteractionEvent;
}

Then, change the signature for interaction_test() to accept the new parameter:

#![allow(unused)]
fn main() {
    #[embassy_executor::task]
    pub async fn integration_test(rx: &'static DisplayChannelWrapper, tx:&'static InteractionChannelWrapper) {
}

Now, unlike the rx parameter that we use within the body of the function, we need this tx parameter available to us while we are in the test code -- and therefore the ITest structure itself, so we need to add it as a member and pass it in on the constructor:

#![allow(unused)]
fn main() {
        struct ITest {
            reporter: TestReporter,
            tx: &'static InteractionChannelWrapper,
            ...
        }
}

and

#![allow(unused)]
fn main() {
        impl ITest {
            pub fn new(tx:&'static InteractionChannelWrapper) -> Self {
                let mut reporter = TestReporter::new();
                reporter.start_test_section(); // start out with a new section
                Self {
                    reporter,
                    tx,
                    ...
}

and pass tx in the ITest constructor in this code at the bottom of the integration_test() function:

#![allow(unused)]
fn main() {
        let mut r = DisplayRenderer::new(RenderMode::IntegrationTest);
        r.set_test_tap(ITest::new(tx)).unwrap();
        r.run(rx).await;
}

Note that the rx (Display) Channel is consumed entirely within the DisplayRenderer run loop, whereas our tx (Interaction) Channel must be available to us in ITest for ad-hoc sending of InteractionEvent messages within our test steps, thus the way we've bifurcated the usage of these here.

Now we are set up to call on interaction event to increase and decrease the load, as we will use in the next test.

Our TestStep enum for this is RaiseLoadAndCheckTemp. Create a new member function to handle this:

#![allow(unused)]
fn main() {
fn raise_load_and_check_temp(&mut self, mins_passed:f32, draw_watts: f32, temp_c:f32) -> TestStep {
    let reporter = &mut self.reporter;

    TestStep::EndAndReport
}
}

We'll fill it out later. First, we need to add some helper members we can use to track time and temperature.

Add these members to the ITest struct:

#![allow(unused)]
fn main() {
            mark_time: Option<f32>,
            mark_temp: Option<f32>,
}

and initialize them as None:

#![allow(unused)]
fn main() {
                    mark_time: None,
                    mark_temp: None,
}

Now, fill out our raise_load_and_check_temp function to look like this:

#![allow(unused)]
fn main() {
            fn raise_load_and_check_temp(&mut self, mins_passed:f32, draw_watts: f32, temp_c:f32) -> TestStep {

                let reporter = &mut self.reporter;

                if self.mark_time == None {
                    self.mark_time = Some(mins_passed);
                    self.mark_temp = Some(temp_c);
                }
                
                if draw_watts < 20.0 { // raise to something above 20 then stop pumping it up
                    let _ = self.tx.try_send(InteractionEvent::LoadUp);
                    return TestStep::RaiseLoadAndCheckTemp                    
                }
                let mt = *self.mark_time.get_or_insert(mins_passed);
                let time_at_charge = if mins_passed > mt { mins_passed - mt } else { 0.0 };
                if time_at_charge > 0.5 { // after about 30 seconds, check temperature
                    let temp_raised = self.mark_temp.map_or(0.0, |mt| if temp_c > mt { temp_c - mt } else { 0.0 });
                    add_test!(reporter, "Temperature rises on charge", |obs| {
                        expect!(obs, temp_raised > 1.5, "Temp should rise noticeably"); 
                    });
                } else {
                    // keep going
                    return TestStep::RaiseLoadAndCheckTemp
                } 
                // reset in case we want to use these again later
                self.mark_temp = None;
                self.mark_time = None;
                // TestStep::RaiseLoadAndCheckFan   // next step             
                TestStep::EndAndReport
            }
}

What we do here is mark the time when we first get in, then we bump up the the load using our new tx member until we see that the load is something above 20w. At that point we check the time to see if at least 1/2 a minute has passed. Until these conditions are met, we keep returning TestStep::RaiseLoadAndCheckTemp to keep us evaluating this state. Once there, we check how high the temperature has risen since the last check, relative to the marked baseline. We expect it to be around 2 degrees, give or take, so we'll check for 1.5 degrees or more as our test. We then go to the next step (for now, EndAndReport), but before we do we reset our Option marks in case we want to reuse them in subsequent tests.

Remember to change the return value of check_charger_attach to go to TestStep::RaiseLoadAndCheckTemp also, or this test won't fire.

Then add the calling code in the match arms section below:

#![allow(unused)]
fn main() {
                    TestStep::RaiseLoadAndCheckTemp => {
                        let mins_passed = dv.sim_time_ms / 60_000.0;
                        let load_watts = dv.draw_watts;
                        let temp_c = dv.temp_c;

                        self.step = self.raise_load_and_check_temp(mins_passed, load_watts, temp_c);
                    },
}

Checking the Fan

The next test we'll create is similar, but in this case, we'll raise the load (and heat) significantly enough for the system fan to kick in.

Create the member function we'll need for this. it will look much like the previous one in many ways:

#![allow(unused)]
fn main() {
            fn raise_load_and_check_fan(&mut self, mins_passed:f32, draw_watts:f32, temp_c:f32, fan_rpm:u16) -> TestStep {
                let reporter = &mut self.reporter;

                // record time we started this
                if self.mark_time == None {
                    self.mark_time = Some(mins_passed);
                }
                
                if draw_watts < 39.0 { // raise to maximum
                    let _ = self.tx.try_send(InteractionEvent::LoadUp);
                    return TestStep::RaiseLoadAndCheckFan                    
                }
                let mt = *self.mark_time.get_or_insert(mins_passed);
                let time_elapsed = if mins_passed > mt { mins_passed - mt } else { 0.0 };
                if time_elapsed > 0.25 && fan_rpm == 0 { // this should happen relatively quickly
                    add_test!(reporter, "Timed out waiting for fan", |obs| {
                        obs.fail("Time expired");
                    });
                    return TestStep::EndAndReport  // quit tests if we timeout
                }
                
                if fan_rpm > 0 {
                    add_test!(reporter, "Temperature is warm", |obs| {
                        expect!(obs, temp_c >= 28.0, "temp below fan on range");
                    });                    
                    add_test!(reporter, "Fan turns on", |obs| {
                        obs.pass();
                    });
                } else {
                    // keep going
                    return TestStep::RaiseLoadAndCheckFan
                }
                // reset in case we want to use these again later
                self.mark_temp = None;
                self.mark_time = None;
                // TestStep::LowerLoadAndCheckCooling
                TestStep::EndAndReport
            }
}

add the calling case to the match arm:

#![allow(unused)]
fn main() {
                    TestStep::RaiseLoadAndCheckFan => {
                        let mins_passed = dv.sim_time_ms / 60_000.0;
                        let draw_watts = dv.draw_watts;
                        let temp_c = dv.temp_c;
                        let fan_rpm = dv.fan_rpm;

                        self.step = self.raise_load_and_check_fan(mins_passed, draw_watts, temp_c, fan_rpm);
                    },
}

Don't forget to update the next step return of the previous step so that it carries forward to this one.

Time to Chill

Great! Now, let's make sure the temperature goes back down with less demand on the system and that the fan backs off when cooling is complete.

Create the member function

#![allow(unused)]
fn main() {
            fn lower_load_and_check_cooling(&mut self, mins_passed:f32, draw_watts:f32, temp_c:f32, fan_rpm:u16) -> TestStep {
                let reporter = &mut self.reporter;

                // record time and temp when we started this
                if self.mark_time == None {
                    self.mark_time = Some(mins_passed);
                    self.mark_temp = Some(temp_c);
                }
                
                // drop load back to low
                if draw_watts > 10.0 { 
                    let _ = self.tx.try_send(InteractionEvent::LoadDown);
                    return TestStep::LowerLoadAndCheckCooling                    
                }
                // wait a bit
                let mark_time = *self.mark_time.get_or_insert(mins_passed);
                let diff = mins_passed - mark_time;
                if diff < 60.0 { // wait for an hour for it to cool all the way 
                    return TestStep::LowerLoadAndCheckCooling
                }

                add_test!(reporter, "Cooled", |obs| {
                    expect!(obs, draw_watts < 10.0, "Load < 10 W");
                    expect!(obs, temp_c < 25.5, "Temp is < 25.5");
                });
                add_test!(reporter, "Fan turns off", |obs| {
                    expect_eq!(obs, fan_rpm, 0);
                });
                // reset in case we want to use these again later
                self.mark_temp = None;
                self.mark_time = None;
                TestStep::EndAndReport
            }
}

and the caller in the match arm:

#![allow(unused)]
fn main() {
                    TestStep::LowerLoadAndCheckCooling => {
                        let mins_passed = dv.sim_time_ms / 60_000.0;
                        let draw_watts = dv.draw_watts;
                        let temp_c = dv.temp_c;
                        let fan_rpm = dv.fan_rpm;

                        self.step = self.lower_load_and_check_cooling(mins_passed, draw_watts, temp_c, fan_rpm);
                    },
}

And again, remember to update the return value for the next step of the load_and_check_fan method to be TestStep::LowerLoadAndCheckCooling so that it chains to this one properly.

Your cargo run --features integration-test should now complete in about 40 seconds and look like this (your output timing may vary slightly):

==================================================
 Test Section Report
 Duration: 35.4803276s

[PASS] Static Values received
[PASS] First Test Data Frame received
[PASS] Check Starting Values
[PASS] Check Charger Attachment
[PASS] Temperature rises on charge
[PASS] Temperature is warm
[PASS] Fan turns on
[PASS] Cooled
[PASS] Fan turns off

 Summary: total=9, passed=9, failed=0
==================================================