From 058535843cc976bdba286bb47b1dc7bae18443ca Mon Sep 17 00:00:00 2001 From: Kurtis Dinelle Date: Thu, 19 Mar 2026 13:53:29 -0700 Subject: [PATCH] thermal-service: Implement runnable service traits --- Cargo.lock | 1 + examples/std/Cargo.lock | 10 ++++ examples/std/Cargo.toml | 1 + examples/std/src/bin/thermal.rs | 33 +++++++------ thermal-service/Cargo.toml | 1 + thermal-service/src/fan.rs | 88 +++++++++++++++++++++++++++++++++ thermal-service/src/lib.rs | 1 - thermal-service/src/sensor.rs | 87 ++++++++++++++++++++++++++++++++ thermal-service/src/task.rs | 18 ------- 9 files changed, 207 insertions(+), 33 deletions(-) delete mode 100644 thermal-service/src/task.rs diff --git a/Cargo.lock b/Cargo.lock index 7f136223..8e2f5d39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2273,6 +2273,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "odp-service-common", "thermal-service-messages", "uuid", ] diff --git a/examples/std/Cargo.lock b/examples/std/Cargo.lock index aa554b46..cf4b7193 100644 --- a/examples/std/Cargo.lock +++ b/examples/std/Cargo.lock @@ -1319,6 +1319,14 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "odp-service-common" +version = "0.1.0" +dependencies = [ + "embedded-services", + "static_cell", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1667,6 +1675,7 @@ dependencies = [ "env_logger", "heapless", "log", + "odp-service-common", "power-policy-interface", "power-policy-service", "static_cell", @@ -1736,6 +1745,7 @@ dependencies = [ "heapless", "log", "mctp-rs", + "odp-service-common", "thermal-service-messages", "uuid", ] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index b63e125f..cbac45e8 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -43,6 +43,7 @@ embedded-sensors-hal-async = "0.3.0" embedded-fans-async = "0.2.0" thermal-service = { path = "../../thermal-service", features = ["log", "mock"] } thermal-service-messages = { path = "../../thermal-service-messages" } +odp-service-common = { path = "../../odp-service-common" } env_logger = "0.11.8" log = "0.4.14" diff --git a/examples/std/src/bin/thermal.rs b/examples/std/src/bin/thermal.rs index 93f899e7..f7585071 100644 --- a/examples/std/src/bin/thermal.rs +++ b/examples/std/src/bin/thermal.rs @@ -22,11 +22,26 @@ async fn run(spawner: Spawner) { let fans = FANS.init([fan.device()]); static STORAGE: OnceLock> = OnceLock::new(); - let service = ts::Service::init(&STORAGE, sensors, fans).await; + let thermal_service = ts::Service::init(&STORAGE, sensors, fans).await; - spawner.must_spawn(sensor_task(service, sensor)); - spawner.must_spawn(fan_task(service, fan)); - spawner.must_spawn(monitor(service)); + let _fan_service = odp_service_common::spawn_service!( + spawner, + ts::fan::Service<'static, ts::mock::fan::MockFan, 16>, + ts::fan::InitParams { fan, thermal_service } + ) + .expect("Failed to spawn fan service"); + + let _sensor_service = odp_service_common::spawn_service!( + spawner, + ts::sensor::Service<'static, ts::mock::sensor::MockSensor, 16>, + ts::sensor::InitParams { + sensor, + thermal_service + } + ) + .expect("Failed to spawn sensor service"); + + spawner.must_spawn(monitor(thermal_service)); } fn main() { @@ -39,16 +54,6 @@ fn main() { }); } -#[embassy_executor::task] -async fn sensor_task(service: &'static ts::Service<'static>, sensor: &'static ts::mock::TsMockSensor) { - ts::task::sensor_task(sensor, service).await -} - -#[embassy_executor::task] -async fn fan_task(service: &'static ts::Service<'static>, fan: &'static ts::mock::TsMockFan) { - ts::task::fan_task(fan, service).await; -} - #[embassy_executor::task] async fn monitor(service: &'static ts::Service<'static>) { loop { diff --git a/thermal-service/Cargo.toml b/thermal-service/Cargo.toml index 97eacd2f..1da4f8e6 100644 --- a/thermal-service/Cargo.toml +++ b/thermal-service/Cargo.toml @@ -17,6 +17,7 @@ embedded-hal-async.workspace = true embedded-hal.workspace = true embedded-services.workspace = true heapless.workspace = true +odp-service-common.workspace = true thermal-service-messages.workspace = true uuid.workspace = true embedded-fans-async = "0.2.0" diff --git a/thermal-service/src/fan.rs b/thermal-service/src/fan.rs index 9f90cc8f..7ba11faf 100644 --- a/thermal-service/src/fan.rs +++ b/thermal-service/src/fan.rs @@ -516,3 +516,91 @@ impl Fan { } } } + +/// The memory resources required by the fan. +pub struct Resources<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: Option>, +} + +// Note: We can't derive Default unless we trait bound T by Default, +// but we don't want that restriction since the default is just the None case +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Default for Resources<'hw, T, SAMPLE_BUF_LEN> { + fn default() -> Self { + Self { inner: None } + } +} + +struct ServiceInner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + fan: &'hw Fan, + thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> ServiceInner<'hw, T, SAMPLE_BUF_LEN> { + fn new(init_params: InitParams<'hw, T, SAMPLE_BUF_LEN>) -> Self { + Self { + fan: init_params.fan, + thermal_service: init_params.thermal_service, + } + } + + fn fan(&self) -> &Fan { + self.fan + } +} + +/// A task runner for a fan. Users must run this in an embassy task or similar async execution context. +pub struct Runner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + service: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::ServiceRunner<'hw> + for Runner<'hw, T, SAMPLE_BUF_LEN> +{ + async fn run(self) -> embedded_services::Never { + loop { + let _ = embassy_futures::join::join3( + self.service.fan.handle_rx(), + self.service.fan.handle_sampling(), + self.service.fan.handle_auto_control(self.service.thermal_service), + ) + .await; + } + } +} + +/// Fan service control handle. +pub struct Service<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Service<'hw, T, SAMPLE_BUF_LEN> { + /// Get a reference to the inner fan. + pub fn fan(&self) -> &Fan { + self.inner.fan() + } +} + +/// Parameters required to initialize a fan service. +pub struct InitParams<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + /// The underlying `Fan` wrapper this service will control. + pub fan: &'hw Fan, + /// The thermal service handle for this fan to communicate with a sensor. + pub thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::Service<'hw> + for Service<'hw, T, SAMPLE_BUF_LEN> +{ + type Runner = Runner<'hw, T, SAMPLE_BUF_LEN>; + type Resources = Resources<'hw, T, SAMPLE_BUF_LEN>; + type ErrorType = Error; + type InitParams = InitParams<'hw, T, SAMPLE_BUF_LEN>; + + async fn new( + service_storage: &'hw mut Self::Resources, + init_params: Self::InitParams, + ) -> Result<(Self, Self::Runner), Self::ErrorType> { + let service = service_storage.inner.insert(ServiceInner::new(init_params)); + Ok((Self { inner: service }, Runner { service })) + } +} diff --git a/thermal-service/src/lib.rs b/thermal-service/src/lib.rs index dc8f70c9..55d816f9 100644 --- a/thermal-service/src/lib.rs +++ b/thermal-service/src/lib.rs @@ -12,7 +12,6 @@ pub mod fan; pub mod mock; pub mod mptf; pub mod sensor; -pub mod task; pub mod utils; /// Thermal error diff --git a/thermal-service/src/sensor.rs b/thermal-service/src/sensor.rs index 69562f34..1771d14d 100644 --- a/thermal-service/src/sensor.rs +++ b/thermal-service/src/sensor.rs @@ -484,3 +484,90 @@ impl Sensor { } } } + +/// The memory resources required by the sensor. +pub struct Resources<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: Option>, +} + +// Note: We can't derive Default unless we trait bound T by Default, +// but we don't want that restriction since the default is just the None case +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Default for Resources<'hw, T, SAMPLE_BUF_LEN> { + fn default() -> Self { + Self { inner: None } + } +} + +struct ServiceInner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + sensor: &'hw Sensor, + thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> ServiceInner<'hw, T, SAMPLE_BUF_LEN> { + fn new(init_params: InitParams<'hw, T, SAMPLE_BUF_LEN>) -> Self { + Self { + sensor: init_params.sensor, + thermal_service: init_params.thermal_service, + } + } + + fn sensor(&self) -> &Sensor { + self.sensor + } +} + +/// A task runner for a sensor. Users must run this in an embassy task or similar async execution context. +pub struct Runner<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + service: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::ServiceRunner<'hw> + for Runner<'hw, T, SAMPLE_BUF_LEN> +{ + async fn run(self) -> embedded_services::Never { + loop { + let _ = embassy_futures::join::join( + self.service.sensor.handle_rx(), + self.service.sensor.handle_sampling(self.service.thermal_service), + ) + .await; + } + } +} + +/// Sensor service control handle. +pub struct Service<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + inner: &'hw ServiceInner<'hw, T, SAMPLE_BUF_LEN>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> Service<'hw, T, SAMPLE_BUF_LEN> { + /// Get a reference to the inner sensor. + pub fn sensor(&self) -> &Sensor { + self.inner.sensor() + } +} + +/// Parameters required to initialize a sensor service. +pub struct InitParams<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> { + /// The underlying `Sensor` wrapper this service will control. + pub sensor: &'hw Sensor, + /// The thermal service handle for this sensor to communicate events to. + pub thermal_service: &'hw crate::Service<'hw>, +} + +impl<'hw, T: Controller, const SAMPLE_BUF_LEN: usize> odp_service_common::runnable_service::Service<'hw> + for Service<'hw, T, SAMPLE_BUF_LEN> +{ + type Runner = Runner<'hw, T, SAMPLE_BUF_LEN>; + type Resources = Resources<'hw, T, SAMPLE_BUF_LEN>; + type ErrorType = Error; + type InitParams = InitParams<'hw, T, SAMPLE_BUF_LEN>; + + async fn new( + service_storage: &'hw mut Self::Resources, + init_params: Self::InitParams, + ) -> Result<(Self, Self::Runner), Self::ErrorType> { + let service = service_storage.inner.insert(ServiceInner::new(init_params)); + Ok((Self { inner: service }, Runner { service })) + } +} diff --git a/thermal-service/src/task.rs b/thermal-service/src/task.rs deleted file mode 100644 index 29e413f4..00000000 --- a/thermal-service/src/task.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub async fn fan_task<'hw, T: crate::fan::Controller, const SAMPLE_BUF_LEN: usize>( - fan: &crate::fan::Fan, - thermal_service: &crate::Service<'hw>, -) { - let _ = embassy_futures::join::join3( - fan.handle_rx(), - fan.handle_sampling(), - fan.handle_auto_control(thermal_service), - ) - .await; -} - -pub async fn sensor_task<'hw, T: crate::sensor::Controller, const SAMPLE_BUF_LEN: usize>( - sensor: &crate::sensor::Sensor, - thermal_service: &crate::Service<'hw>, -) { - let _ = embassy_futures::join::join(sensor.handle_rx(), sensor.handle_sampling(thermal_service)).await; -}