blob: f2963ba714753f8d0446400bbd9d4a3a9d3879b0 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::common_utils::result_debug_panic::ResultDebugPanic;
use crate::error::PowerManagerError;
use crate::log_if_err;
use crate::message::{Message, MessageReturn};
use crate::node::Node;
use crate::ok_or_default_err;
use crate::types::{Celsius, Nanoseconds, Seconds};
use anyhow::{format_err, Error};
use async_trait::async_trait;
use async_utils::event::Event as AsyncEvent;
use fidl_fuchsia_hardware_temperature as ftemperature;
use fuchsia_async as fasync;
use fuchsia_inspect::{self as inspect, NumericProperty, Property};
use fuchsia_inspect_contrib::{inspect_log, nodes::BoundedListNode};
use fuchsia_zircon as zx;
use serde_derive::Deserialize;
use serde_json as json;
use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use tracing::*;
/// Node: TemperatureHandler
///
/// Summary: Responds to temperature requests from other nodes by polling the specified driver
/// using the temperature FIDL protocol. May be configured to cache the polled temperature
/// for a while to prevent excessive polling of the sensor. (Polling errors are not
/// cached.)
///
/// Handles Messages:
/// - ReadTemperature
///
/// Sends Messages: N/A
///
/// FIDL dependencies:
/// - fuchsia.hardware.temperature: the node uses this protocol to query the temperature driver
/// specified by `driver_path` in the TemperatureHandler constructor
/// A builder for constructing the TemperatureHandler node
pub struct TemperatureHandlerBuilder<'a> {
driver_path: Option<String>,
driver_proxy: Option<ftemperature::DeviceProxy>,
cache_duration: Option<zx::Duration>,
inspect_root: Option<&'a inspect::Node>,
}
impl<'a> TemperatureHandlerBuilder<'a> {
#[cfg(test)]
pub fn new() -> Self {
Self {
driver_path: Some("/test/driver/path".to_string()),
driver_proxy: None,
cache_duration: None,
inspect_root: None,
}
}
#[cfg(test)]
pub fn driver_proxy(mut self, proxy: ftemperature::DeviceProxy) -> Self {
self.driver_proxy = Some(proxy);
self
}
#[cfg(test)]
pub fn cache_duration(mut self, duration: zx::Duration) -> Self {
self.cache_duration = Some(duration);
self
}
#[cfg(test)]
fn inspect_root(mut self, inspect_root: &'a inspect::Node) -> Self {
self.inspect_root = Some(inspect_root);
self
}
pub fn new_from_json(json_data: json::Value, _nodes: &HashMap<String, Rc<dyn Node>>) -> Self {
#[derive(Deserialize)]
struct Config {
driver_path: String,
cache_duration_ms: u32,
}
#[derive(Deserialize)]
struct JsonData {
config: Config,
}
let data: JsonData = json::from_value(json_data).unwrap();
Self {
driver_path: Some(data.config.driver_path),
driver_proxy: None,
cache_duration: Some(zx::Duration::from_millis(data.config.cache_duration_ms as i64)),
inspect_root: None,
}
}
pub fn build(self) -> Result<Rc<TemperatureHandler>, Error> {
let driver_path = ok_or_default_err!(self.driver_path).or_debug_panic()?;
// Default `cache_duration`: 0
let cache_duration = self.cache_duration.unwrap_or(zx::Duration::from_millis(0));
let mutable_inner = MutableInner {
last_temperature: Celsius(std::f64::NAN),
last_poll_time: fasync::Time::INFINITE_PAST,
driver_proxy: self.driver_proxy,
debug_temperature_override: None,
};
// Optionally use the default inspect root node
let inspect_root = self.inspect_root.unwrap_or(inspect::component::inspector().root());
Ok(Rc::new(TemperatureHandler {
init_done: AsyncEvent::new(),
driver_path: driver_path.clone(),
mutable_inner: RefCell::new(mutable_inner),
cache_duration,
inspect: InspectData::new(
inspect_root,
format!("TemperatureHandler ({})", driver_path),
),
}))
}
#[cfg(test)]
pub async fn build_and_init(self) -> Rc<TemperatureHandler> {
let node = self.build().unwrap();
node.init().await.unwrap();
node
}
}
pub struct TemperatureHandler {
/// Signalled after `init()` has completed. Used to ensure node doesn't process messages until
/// its `init()` has completed.
init_done: AsyncEvent,
/// Path to the temperature driver that this node reads from.
driver_path: String,
/// Mutable inner state.
mutable_inner: RefCell<MutableInner>,
/// Duration for which a polled temperature is cached. This prevents excessive polling of the
/// sensor.
cache_duration: zx::Duration,
/// A struct for managing Component Inspection data
inspect: InspectData,
}
impl TemperatureHandler {
fn handle_get_driver_path(&self) -> Result<MessageReturn, PowerManagerError> {
Ok(MessageReturn::GetDriverPath(self.driver_path.clone()))
}
async fn handle_read_temperature(&self) -> Result<MessageReturn, PowerManagerError> {
fuchsia_trace::duration!(
c"power_manager",
c"TemperatureHandler::handle_read_temperature",
"driver" => self.driver_path.as_str()
);
self.init_done.wait().await;
if let Some(temperature) = self.mutable_inner.borrow().debug_temperature_override {
return Ok(MessageReturn::ReadTemperature(temperature));
}
let last_poll_time = self.mutable_inner.borrow().last_poll_time;
let last_temperature = self.mutable_inner.borrow().last_temperature;
// If the last temperature value is sufficiently fresh, return it instead of polling.
// Note that if the previous poll generated an error, `last_poll_time` was not updated,
// and (barring clock glitches) a new poll will occur.
if fasync::Time::now() <= last_poll_time + self.cache_duration {
return Ok(MessageReturn::ReadTemperature(last_temperature));
}
let result = self.read_temperature().await;
log_if_err!(
result,
format!("Failed to read temperature from {}", self.driver_path).as_str()
);
fuchsia_trace::instant!(
c"power_manager",
c"TemperatureHandler::read_temperature_result",
fuchsia_trace::Scope::Thread,
"driver" => self.driver_path.as_str(),
"result" => format!("{:?}", result).as_str()
);
match result {
Ok(temperature) => {
let mut inner = self.mutable_inner.borrow_mut();
inner.last_temperature = temperature;
inner.last_poll_time = fasync::Time::now();
self.inspect.log_temperature_reading(temperature);
Ok(MessageReturn::ReadTemperature(temperature))
}
Err(e) => {
self.inspect.read_errors.add(1);
self.inspect.last_read_error.set(format!("{}", e).as_str());
Err(e.into())
}
}
}
async fn read_temperature(&self) -> Result<Celsius, Error> {
fuchsia_trace::duration!(
c"power_manager",
c"TemperatureHandler::read_temperature",
"driver" => self.driver_path.as_str()
);
// Extract `driver_proxy` from `mutable_inner`, returning an error (or asserting in debug)
// if the proxy is missing
let driver_proxy = self
.mutable_inner
.borrow()
.driver_proxy
.as_ref()
.ok_or(format_err!("Missing driver_proxy"))
.or_debug_panic()?
.clone();
let (status, temperature) = driver_proxy.get_temperature_celsius().await.map_err(|e| {
format_err!("{}: get_temperature_celsius IPC failed: {}", self.name(), e)
})?;
zx::Status::ok(status).map_err(|e| {
format_err!("{}: get_temperature_celsius driver returned error: {}", self.name(), e)
})?;
Ok(Celsius(temperature.into()))
}
fn handle_debug_message(
&self,
command: &String,
args: &Vec<String>,
) -> Result<MessageReturn, PowerManagerError> {
let print_help = || {
info!("Supported commands: set_temperature, clear_temperature");
};
match command.as_str() {
"set_temperature_override" => {
let temperature = Celsius(
args.get(0)
.ok_or(PowerManagerError::InvalidArgument(format!(
"Must specify exactly one arg"
)))?
.parse::<f64>()
.map_err(|_| {
PowerManagerError::InvalidArgument(format!(
"Couldn't parse f64 from arg {}",
args[0]
))
})?,
);
info!("Overriding temperature to {} C", temperature.0);
self.mutable_inner.borrow_mut().debug_temperature_override = Some(temperature);
}
"clear_temperature_override" => {
info!("Clearing temperature override");
self.mutable_inner.borrow_mut().debug_temperature_override = None;
}
"help" => {
print_help();
}
e => {
error!("Unsupported command {}", e);
print_help();
return Err(PowerManagerError::Unsupported);
}
}
Ok(MessageReturn::Debug)
}
}
struct MutableInner {
/// Last temperature returned by the handler.
last_temperature: Celsius,
/// Time of the last temperature poll, for determining cache freshness.
last_poll_time: fasync::Time,
/// Proxy to the temperature driver. Populated during `init()` unless previously supplied (in a
/// test).
driver_proxy: Option<ftemperature::DeviceProxy>,
/// Allow the debug service to set an override temperature. If set, `ReadTemperature` will
/// always respond with this temperature.
debug_temperature_override: Option<Celsius>,
}
#[async_trait(?Send)]
impl Node for TemperatureHandler {
fn name(&self) -> String {
format!("TemperatureHandler ({})", self.driver_path)
}
/// Initializes internal state.
///
/// Connects to the temperature driver unless a proxy was already provided (in a test).
async fn init(&self) -> Result<(), Error> {
fuchsia_trace::duration!(c"power_manager", c"TemperatureHandler::init");
// Connect to the temperature driver. Typically this is None, but it may be set by tests.
let driver_proxy = match &self.mutable_inner.borrow().driver_proxy {
Some(p) => p.clone(),
None => fuchsia_component::client::connect_to_protocol_at_path::<
ftemperature::DeviceMarker,
>(&self.driver_path)?,
};
self.mutable_inner.borrow_mut().driver_proxy = Some(driver_proxy);
self.init_done.signal();
Ok(())
}
async fn handle_message(&self, msg: &Message) -> Result<MessageReturn, PowerManagerError> {
match msg {
Message::ReadTemperature => self.handle_read_temperature().await,
Message::GetDriverPath => self.handle_get_driver_path(),
Message::Debug(command, args) => self.handle_debug_message(command, args),
_ => Err(PowerManagerError::Unsupported),
}
}
}
struct InspectData {
temperature_readings: RefCell<BoundedListNode>,
read_errors: inspect::UintProperty,
last_read_error: inspect::StringProperty,
}
impl InspectData {
/// Number of inspect samples to store in the `temperature_readings` BoundedListNode.
// Store the last 60 seconds of temperature readings (https://fxbug.dev/42137879)
const NUM_INSPECT_TEMPERATURE_SAMPLES: usize = 60;
fn new(parent: &inspect::Node, name: String) -> Self {
// Create a local root node and properties
let root = parent.create_child(name);
let temperature_readings = RefCell::new(BoundedListNode::new(
root.create_child("temperature_readings"),
Self::NUM_INSPECT_TEMPERATURE_SAMPLES,
));
let read_errors = root.create_uint("read_temperature_error_count", 0);
let last_read_error = root.create_string("last_read_error", "");
// Pass ownership of the new node to the parent node, otherwise it'll be dropped
parent.record(root);
InspectData { temperature_readings, read_errors, last_read_error }
}
fn log_temperature_reading(&self, temperature: Celsius) {
inspect_log!(self.temperature_readings.borrow_mut(), temperature: temperature.0);
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use assert_matches::assert_matches;
use async_utils::PollExt as _;
use diagnostics_assertions::assert_data_tree;
use diagnostics_assertions::TreeAssertion;
use futures::poll;
use futures::TryStreamExt;
use std::task::Poll;
/// Spawns a new task that acts as a fake temperature driver for testing purposes. Each
/// GetTemperatureCelsius call responds with a value provided by the supplied `get_temperature`
/// closure.
pub fn fake_temperature_driver(
mut get_temperature: impl FnMut() -> Celsius + 'static,
) -> ftemperature::DeviceProxy {
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<ftemperature::DeviceMarker>().unwrap();
fasync::Task::local(async move {
while let Ok(req) = stream.try_next().await {
match req {
Some(ftemperature::DeviceRequest::GetTemperatureCelsius { responder }) => {
let _ =
responder.send(zx::Status::OK.into_raw(), get_temperature().0 as f32);
}
_ => assert!(false),
}
}
})
.detach();
proxy
}
/// Tests that the node can handle the 'ReadTemperature' message as expected. The test
/// checks for the expected temperature value which is returned by the fake temperature driver.
#[fasync::run_singlethreaded(test)]
async fn test_read_temperature() {
// Readings for the fake temperature driver.
let temperature_readings = vec![1.2, 3.4, 5.6, 7.8, 9.0];
// Readings piped through the fake driver will be cast to f32 and back to f64.
let expected_readings: Vec<f64> =
temperature_readings.iter().map(|x| *x as f32 as f64).collect();
// Each ReadTemperature request will respond with the next element from
// `temperature_readings`, wrapping back around when the end of the vector is reached.
let mut index = 0;
let get_temperature = move || {
let value = temperature_readings[index];
index = (index + 1) % temperature_readings.len();
Celsius(value)
};
let node = TemperatureHandlerBuilder::new()
.driver_proxy(fake_temperature_driver(get_temperature))
.build_and_init()
.await;
// Send ReadTemperature message and check for expected value.
for expected_reading in expected_readings {
let result = node.handle_message(&Message::ReadTemperature).await;
let temperature = result.unwrap();
if let MessageReturn::ReadTemperature(t) = temperature {
assert_eq!(t.0, expected_reading);
} else {
assert!(false);
}
}
}
#[test]
fn test_caching() -> Result<(), Error> {
let mut executor = fasync::TestExecutor::new_with_fake_time();
executor.set_fake_time(Seconds(0.0).into());
let sensor_temperature = Rc::new(Cell::new(Celsius(0.0)));
let sensor_temperature_clone = sensor_temperature.clone();
let get_temperature = move || sensor_temperature_clone.get();
let node = executor
.run_until_stalled(&mut Box::pin(
TemperatureHandlerBuilder::new()
.driver_proxy(fake_temperature_driver(get_temperature))
.cache_duration(zx::Duration::from_millis(500))
.build_and_init(),
))
.unwrap();
let run = move |executor: &mut fasync::TestExecutor, duration_ms: i64| {
executor.set_fake_time(executor.now() + zx::Duration::from_millis(duration_ms));
let poll =
executor.run_until_stalled(&mut node.handle_message(&Message::ReadTemperature));
if let Poll::Ready(Ok(MessageReturn::ReadTemperature(temperature))) = poll {
temperature
} else {
panic!("Unexpected poll: {:?}", poll);
}
};
// When advancing longer than the cache duration, we'll always poll the sensor.
sensor_temperature.set(Celsius(20.0));
assert_eq!(run(&mut executor, 1000), Celsius(20.0));
sensor_temperature.set(Celsius(21.0));
assert_eq!(run(&mut executor, 1000), Celsius(21.0));
// If insufficient time has passed, we'll see the cached value.
sensor_temperature.set(Celsius(22.0));
assert_eq!(run(&mut executor, 200), Celsius(21.0));
assert_eq!(run(&mut executor, 200), Celsius(21.0));
assert_eq!(run(&mut executor, 200), Celsius(22.0));
Ok(())
}
/// Tests that an unsupported message is handled gracefully and an error is returned.
#[fasync::run_singlethreaded(test)]
async fn test_unsupported_msg() {
let node = TemperatureHandlerBuilder::new()
.driver_proxy(fake_temperature_driver(|| Celsius(0.0)))
.build_and_init()
.await;
match node.handle_message(&Message::NotifyMicEnabledChanged(true)).await {
Err(PowerManagerError::Unsupported) => {}
e => panic!("Unexpected return value: {:?}", e),
}
}
/// Tests for the presence and correctness of dynamically-added inspect data
#[fasync::run_singlethreaded(test)]
async fn test_inspect_data() {
let inspector = inspect::Inspector::default();
let node = TemperatureHandlerBuilder::new()
.driver_proxy(fake_temperature_driver(|| Celsius(30.0)))
.inspect_root(inspector.root())
.build_and_init()
.await;
// The node will read the current temperature and log the sample into Inspect. Read enough
// samples to test that the correct number of samples are logged and older ones are dropped.
for _ in 0..InspectData::NUM_INSPECT_TEMPERATURE_SAMPLES + 10 {
node.handle_message(&Message::ReadTemperature).await.unwrap();
}
let mut root = TreeAssertion::new("TemperatureHandler (/test/driver/path)", false);
let mut temperature_readings = TreeAssertion::new("temperature_readings", true);
// Since we read 10 more samples than our limit allows, the first 10 should be dropped. So
// test that the sample numbering starts at 10 and continues for the expected number of
// samples.
for i in 10..InspectData::NUM_INSPECT_TEMPERATURE_SAMPLES + 10 {
let mut sample_child = TreeAssertion::new(&i.to_string(), true);
sample_child.add_property_assertion("temperature", Box::new(30.0));
sample_child
.add_property_assertion("@time", Box::new(diagnostics_assertions::AnyProperty));
temperature_readings.add_child_assertion(sample_child);
}
root.add_child_assertion(temperature_readings);
assert_data_tree!(inspector, root: { root, });
}
/// Tests that well-formed configuration JSON does not panic the `new_from_json` function.
#[fasync::run_singlethreaded(test)]
async fn test_new_from_json() {
let json_data = json::json!({
"type": "TemperatureHandler",
"name": "temperature",
"config": {
"driver_path": "/dev/class/temperature/000",
"cache_duration_ms": 1000
}
});
let _ = TemperatureHandlerBuilder::new_from_json(json_data, &HashMap::new());
}
/// Tests that the node correctly reports its driver path.
#[fasync::run_singlethreaded(test)]
async fn test_get_driver_path() {
let node = TemperatureHandlerBuilder::new()
.driver_proxy(fake_temperature_driver(|| Celsius(0.0)))
.build_and_init()
.await;
let driver_path = match node.handle_message(&Message::GetDriverPath).await.unwrap() {
MessageReturn::GetDriverPath(driver_path) => driver_path,
e => panic!("Unexpected message response: {:?}", e),
};
// "TestTemperatureHandler" is the driver path assigned in
// `TemperatureHandlerBuilder::new()`
assert_eq!(driver_path, "/test/driver/path".to_string());
}
/// Tests that messages sent to the node are asynchronously blocked until the node's `init()`
/// has completed.
#[fasync::run_singlethreaded(test)]
async fn test_require_init() {
// Create the node without `init()`
let node = TemperatureHandlerBuilder::new()
.driver_proxy(fake_temperature_driver(|| Celsius(0.0)))
.build()
.unwrap();
// Future to send the node a message
let mut message_future = node.handle_message(&Message::ReadTemperature);
assert!(poll!(&mut message_future).is_pending());
assert_matches!(node.init().await, Ok(()));
assert_matches!(message_future.await, Ok(MessageReturn::ReadTemperature(Celsius(_))));
}
/// Tests that the debug temperature override command correctly overrides temperature and can be
/// cleared as expected.
#[fasync::run_singlethreaded(test)]
async fn test_debug_temperature_override() {
let node = TemperatureHandlerBuilder::new()
.driver_proxy(fake_temperature_driver(|| Celsius(10.0)))
.build_and_init()
.await;
assert_matches!(
node.handle_message(&Message::ReadTemperature).await.unwrap(),
MessageReturn::ReadTemperature(Celsius(t)) if t == 10.0
);
assert_matches!(
node.handle_message(&Message::Debug(
"set_temperature_override".into(),
vec!["20.0".into()]
))
.await
.unwrap(),
MessageReturn::Debug
);
assert_matches!(
node.handle_message(&Message::ReadTemperature).await.unwrap(),
MessageReturn::ReadTemperature(Celsius(t)) if t == 20.0
);
assert_matches!(
node.handle_message(&Message::Debug("clear_temperature_override".into(), vec![]))
.await
.unwrap(),
MessageReturn::Debug
);
assert_matches!(
node.handle_message(&Message::ReadTemperature).await.unwrap(),
MessageReturn::ReadTemperature(Celsius(t)) if t == 10.0
);
}
}
/// Contains both the raw and filtered temperature values returned from the TemperatureFilter
/// `get_temperature` function.
#[derive(PartialEq, Debug)]
pub struct TemperatureReadings {
pub raw: Celsius,
pub filtered: Celsius,
}
/// Wrapper for reading and filtering temperature samples from a TemperatureHandler node.
pub struct TemperatureFilter {
/// Filter time constant. A value of 0 effectively disables filtering.
time_constant: Seconds,
/// Previous sample temperature
prev_temperature: Cell<Option<Celsius>>,
/// Previous sample timestamp
prev_timestamp: Cell<Nanoseconds>,
/// TemperatureHandler node that is used to read temperature
temperature_handler: Rc<dyn Node>,
}
impl TemperatureFilter {
/// Constucts a new TemperatureFilter with the specified TemperatureHandler node and filter time
/// constant.
pub fn new(temperature_handler: Rc<dyn Node>, time_constant: Seconds) -> Self {
Self {
time_constant,
prev_temperature: Cell::new(None),
prev_timestamp: Cell::new(Nanoseconds(0)),
temperature_handler,
}
}
/// Reads a new temperature sample and returns a Temperature instance containing both the raw
/// and filtered temperature values.
pub async fn get_temperature(
&self,
timestamp: Nanoseconds,
) -> Result<TemperatureReadings, Error> {
fuchsia_trace::duration!(c"power_manager", c"TemperatureFilter::get_temperature");
let raw_temperature = self.read_temperature().await?;
let filtered_temperature = match self.prev_temperature.get() {
Some(prev_temperature) => Self::low_pass_filter(
raw_temperature,
prev_temperature,
(timestamp - self.prev_timestamp.get()).into(),
self.time_constant,
),
None => raw_temperature,
};
self.prev_temperature.set(Some(filtered_temperature));
self.prev_timestamp.set(timestamp);
Ok(TemperatureReadings { raw: raw_temperature, filtered: filtered_temperature })
}
/// Queries the current temperature from the temperature handler node
async fn read_temperature(&self) -> Result<Celsius, Error> {
fuchsia_trace::duration!(c"power_manager", c"TemperatureFilter::read_temperature");
match self.temperature_handler.handle_message(&Message::ReadTemperature).await {
Ok(MessageReturn::ReadTemperature(t)) => Ok(t),
Ok(r) => Err(format_err!("ReadTemperature had unexpected return value: {:?}", r)),
Err(e) => Err(format_err!("ReadTemperature failed: {:?}", e)),
}
}
/// Filters the input temperature value using the specified previous temperature value `y_prev`,
/// `time_delta`, and `time_constant`.
fn low_pass_filter(
y: Celsius,
y_prev: Celsius,
time_delta: Seconds,
time_constant: Seconds,
) -> Celsius {
if time_constant == Seconds(0.0) {
y
} else {
Celsius(y_prev.0 + (time_delta.0 / time_constant.0) * (y.0 - y_prev.0))
}
}
}
#[cfg(test)]
mod temperature_filter_tests {
use super::*;
use crate::test::mock_node::{MessageMatcher, MockNodeMaker};
use crate::{msg_eq, msg_ok_return};
use fuchsia_async as fasync;
/// Tests the low_pass_filter function for correctness.
#[test]
fn test_low_pass_filter() {
let y_0 = Celsius(0.0);
let y_1 = Celsius(10.0);
let time_delta = Seconds(1.0);
let time_constant = Seconds(10.0);
assert_eq!(
TemperatureFilter::low_pass_filter(y_1, y_0, time_delta, time_constant),
Celsius(1.0)
);
// Filter constant of 0 is valid and should be treated as "no filtering"
assert_eq!(TemperatureFilter::low_pass_filter(y_1, y_0, time_delta, Seconds(0.0)), y_1);
}
/// Tests that the TemperatureFilter `get_temperature` function queries the TemperatureHandler
/// node and returns the expected raw and filtered temperature values.
#[fasync::run_singlethreaded(test)]
async fn test_get_temperature() {
let mut mock_maker = MockNodeMaker::new();
let temperature_node = mock_maker.make(
"Temperature",
vec![
(msg_eq!(ReadTemperature), msg_ok_return!(ReadTemperature(Celsius(50.0)))),
(msg_eq!(ReadTemperature), msg_ok_return!(ReadTemperature(Celsius(80.0)))),
],
);
// Create a TemperatureFilter instance using 5 seconds as the filter constant
let filter = TemperatureFilter::new(temperature_node, Seconds(5.0));
// The first reading should return identical raw/filtered values
assert_eq!(
filter.get_temperature(Nanoseconds(0)).await.unwrap(),
TemperatureReadings { raw: Celsius(50.0), filtered: Celsius(50.0) }
);
// The next reading should return the raw value (80C) along with the calculated filtered
// value for the given elapsed time (1 second)
assert_eq!(
filter.get_temperature(Seconds(1.0).into()).await.unwrap(),
TemperatureReadings { raw: Celsius(80.0), filtered: Celsius(56.0) }
);
}
}