From 04c3539641fb71ebe455eb5f54d865e1de14a088 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 20 Feb 2026 09:53:44 -0800 Subject: [PATCH 1/5] Add support for uniform spawning of services, convert TAD+eSPI --- embedded-service/src/lib.rs | 1 + embedded-service/src/service.rs | 133 ++++++++++++++++++++++ espi-service/src/espi_service.rs | 59 ++++++---- espi-service/src/lib.rs | 3 - espi-service/src/task.rs | 12 -- examples/rt685s-evk/src/bin/time_alarm.rs | 19 +--- time-alarm-service/src/lib.rs | 35 +++--- time-alarm-service/src/task.rs | 9 -- time-alarm-service/tests/tad_test.rs | 8 +- 9 files changed, 200 insertions(+), 79 deletions(-) create mode 100644 embedded-service/src/service.rs delete mode 100644 espi-service/src/task.rs delete mode 100644 time-alarm-service/src/task.rs diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index f20d6e89..c5d15a58 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -23,6 +23,7 @@ pub mod ipc; pub mod keyboard; pub mod power; pub mod relay; +pub mod service; pub mod sync; pub mod type_c; diff --git a/embedded-service/src/service.rs b/embedded-service/src/service.rs new file mode 100644 index 00000000..3b9b6178 --- /dev/null +++ b/embedded-service/src/service.rs @@ -0,0 +1,133 @@ +//! This module contains helper traits and functions for services that run on the EC. + +/// A trait for a service that can be run on the EC. +/// Implementations of RunnableService should have an init() function to construct the service that +/// returns a Runner, which the user is expected to spawn a task for. +pub trait RunnableService<'hw> { + /// A token type used to restrict users from spawning more than one service runner. Services will generally + /// define this as a zero-sized type and only provide a constructor for it that is private to the service module, + /// which prevents users from constructing their own tokens and spawning multiple runners. + /// Most services should consider using the `impl_runner_creation_token!` macro to do this automatically. + type RunnerCreationToken; + + /// Run the service event loop. This future never completes. + fn run( + &'hw self, + _creation_token: Self::RunnerCreationToken, + ) -> impl core::future::Future + 'hw; +} + +/// A handle that must be passed to a spawned task and `.run().await`'d to drive the service. +/// Dropping this without calling `runner.run().await` means the service will not process events +pub struct ServiceRunner<'hw, T: RunnableService<'hw>> { + service: &'hw T, + creation_token: T::RunnerCreationToken, // This token is used to ensure that only the service can create a runner for itself. It's probably a zero-sized type. +} + +impl<'hw, T: RunnableService<'hw>> ServiceRunner<'hw, T> { + /// Runs the service event loop. This future never completes. + pub async fn run(self) -> crate::Never { + self.service.run(self.creation_token).await + } + + /// Constructs a new service runner. This is something the service will do in its init function; users of + /// the service should not need to call this directly. + pub fn new(service: &'hw T, token: T::RunnerCreationToken) -> Self { + Self { + service, + creation_token: token, + } + } +} + +/// Generates a default implementation of a runner creation token for a service. This token is used to ensure that +/// only the service can create a runner for itself, and therefore it can control the number of tasks that a user is +/// allowed to spawn to run the service (e.g. if the service is not designed to be run by multiple tasks, it can use +/// this token to prevent that). +/// +/// Most services will want to use this macro to generate a simple zero-sized token type - it needs to be a macro invoked +/// in the service module rather than a generic type in this module because the constructor needs to be private to the +/// service module to prevent users from constructing their own tokens and spawning multiple runners. +/// +/// Arguments: +/// - token_name: The name of the token type to generate. +#[macro_export] +macro_rules! impl_runner_creation_token { + ($token_name:ident) => { + /// A token type used to restrict users from spawning more than one service runner. + pub struct $token_name { + _private: (), + } + + impl $token_name { + fn new() -> Self { + Self { _private: () } + } + } + }; +} + +pub use impl_runner_creation_token; + +/// Initializes a service, creates an embassy task to run it, and spawns that task. +/// +/// This macro handles the boilerplate of: +/// 1. Creating a `static` [`OnceLock`](embassy_sync::once_lock::OnceLock) to hold the service +/// 2. Calling the service's `init()` method +/// 3. Defining an [`embassy_executor::task`] to run the service +/// 4. Spawning the task on the provided executor +/// +/// Returns a Result where Error is the error type of $service_ty::init(). +/// +/// Note that for a service to be supported, it must have the following properties: // TODO figure out if this should be a trait. Would require a single associated-type arg rather than letting each service define its own init list though... +/// 1. Implements the RunnableService trait +/// 2. Has an init() function with the following properties: +/// i. Takes as its first argument a &OnceLock +/// ii. Returns a Result<(reference-to-service, service-runner), Error> where the service-runner +/// is an instance of RunnableService. +/// +/// Arguments +/// +/// - spawner: An [`embassy_executor::Spawner`]. +/// - service_ty: The service type, wrapped in brackets to allow generic arguments +/// (e.g. `[my_crate::Service<'static>]`). +/// - init_args: The arguments to pass to `Service::init()`, excluding the `OnceLock` argument, which is codegenned. +/// +/// Example: +/// +/// ```ignore +/// let time_service = embedded_services::spawn_service!( +/// time_alarm_task, +/// spawner, +/// [time_alarm_service::Service<'static>], +/// dt_clock, tz, ac_expiration, ac_policy, dc_expiration, dc_policy +/// ).expect("failed to initialize time_alarm service"); +/// ``` +#[macro_export] +macro_rules! spawn_service { + ($spawner:expr, [ $($service_ty:tt)* ], $($init_args:expr),* $(,)?) => { + { + static SERVICE: embassy_sync::once_lock::OnceLock<$($service_ty)*> = embassy_sync::once_lock::OnceLock::new(); + match <$($service_ty)*>::init( + &SERVICE, + $($init_args),* + ) + .await { + Ok((service_ref, runner)) => { + #[embassy_executor::task] + async fn service_task_fn( + runner: $crate::service::ServiceRunner<'static, $($service_ty)*>, + ) { + runner.run().await; + } + + $spawner.must_spawn(service_task_fn(runner)); + Ok(service_ref) + }, + Err(e) => Err(e) + } + } + }; +} + +pub use spawn_service; diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 479059e9..872816a2 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -75,6 +75,34 @@ pub struct Service { // ///////// COMMON FUNCTIONS /////////// + +embedded_services::impl_runner_creation_token!(RunnerCreationToken); + +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_services::service::RunnableService<'hw> + for Service +{ + type RunnerCreationToken = RunnerCreationToken; + async fn run(&'hw self, _: Self::RunnerCreationToken) -> embedded_services::Never { + let mut espi = self.espi.lock().await; + loop { + let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await; + + match event { + embassy_futures::select::Either::First(controller_event) => { + self.process_controller_event(&mut espi, controller_event) + .await + .unwrap_or_else(|e| { + error!("Critical error processing eSPI controller event: {:?}", e); + }); + } + embassy_futures::select::Either::Second(host_msg) => { + self.process_response_to_host_routing(&mut espi, host_msg).await + } + } + } + } +} + impl Service { // TODO a lot of the input lifetimes here have to be static because we have a dependency on the comms system, which requires // that everything that talks over it is 'static. Once we eliminate that dependency, we should be able to relax these lifetimes. @@ -82,10 +110,11 @@ impl Service, mut espi: espi::Espi<'static>, relay_handler: RelayHandler, - ) -> &'static Self { + ) -> Result<(&'static Self, embedded_services::service::ServiceRunner<'static, Self>), core::convert::Infallible> + { espi.wait_for_plat_reset().await; - let result = service_storage.get_or_init(|| Service { + let service = service_storage.get_or_init(|| Service { endpoint: DEPRECATED_comms::Endpoint::uninit(DEPRECATED_comms::EndpointID::External( DEPRECATED_comms::External::Host, )), @@ -94,30 +123,14 @@ impl Service ! { - let mut espi = self.espi.lock().await; - loop { - let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await; - - match event { - embassy_futures::select::Either::First(controller_event) => { - self.process_controller_event(&mut espi, controller_event) - .await - .unwrap_or_else(|e| { - error!("Critical error processing eSPI controller event: {:?}", e); - }); - } - embassy_futures::select::Either::Second(host_msg) => { - self.process_response_to_host_routing(&mut espi, host_msg).await - } - } - } + Ok(( + service, + embedded_services::service::ServiceRunner::new(service, RunnerCreationToken::new()), + )) } // TODO The notification system was not actually used, so this is currently dead code. diff --git a/espi-service/src/lib.rs b/espi-service/src/lib.rs index e8233bc9..c89b89f5 100644 --- a/espi-service/src/lib.rs +++ b/espi-service/src/lib.rs @@ -25,8 +25,5 @@ mod espi_service; #[cfg(not(test))] mod mctp; -#[cfg(not(test))] -pub mod task; - #[cfg(not(test))] pub use espi_service::*; diff --git a/espi-service/src/task.rs b/espi-service/src/task.rs deleted file mode 100644 index ff7f7fe8..00000000 --- a/espi-service/src/task.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::Service; - -// TODO: We currently require that the service has lifetime 'static because we still communicate with -// some services over the legacy comms system, which requires that things that interact with it -// have lifetime 'static. Once we've fully transitioned to the direct async call method of interfacing -// between services, we should be able to relax this requirement to just require that the service has -// the same lifetime as the services it's communicating with. -pub async fn espi_service( - espi_service: &'static Service, -) -> Result { - espi_service.run_service().await -} diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 9b1a8182..80c50cdd 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -1,7 +1,6 @@ #![no_std] #![no_main] -use embassy_sync::once_lock::OnceLock; use embedded_mcu_hal::{ Nvram, time::{Datetime, Month, UncheckedDatetime}, @@ -24,25 +23,17 @@ async fn main(spawner: embassy_executor::Spawner) { embedded_services::init().await; info!("services initialized"); - static TIME_SERVICE: OnceLock = OnceLock::new(); - let time_service = time_alarm_service::Service::init( - &TIME_SERVICE, + let time_service = embedded_services::spawn_service!( + spawner, + [time_alarm_service::Service<'static>], dt_clock, tz, ac_expiration, ac_policy, dc_expiration, - dc_policy, + dc_policy ) - .await - .expect("Failed to initialize time-alarm service"); - - #[embassy_executor::task] - async fn time_alarm_task(service: &'static time_alarm_service::Service<'static>) { - time_alarm_service::task::run_service(service).await - } - - spawner.must_spawn(time_alarm_task(time_service)); + .expect("Failed to spawn time alarm service"); use embedded_services::relay::mctp::impl_odp_mctp_relay_handler; impl_odp_mctp_relay_handler!( diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index 333420ae..ee1c0e9d 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -10,7 +10,6 @@ use embedded_services::GlobalRawMutex; use embedded_services::{info, warn}; use time_alarm_service_messages::*; -pub mod task; mod timer; use timer::Timer; @@ -124,7 +123,7 @@ impl<'hw> Service<'hw> { ac_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, dc_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, dc_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, - ) -> Result<&'hw Service<'hw>, DatetimeClockError> { + ) -> Result<(&'hw Self, embedded_services::service::ServiceRunner<'hw, Self>), DatetimeClockError> { info!("Starting time-alarm service task"); let service = service_storage.get_or_init(|| Service { @@ -160,7 +159,10 @@ impl<'hw> Service<'hw> { service.timers.ac_timer.start(&service.clock_state, true)?; service.timers.dc_timer.start(&service.clock_state, false)?; - Ok(service) + Ok(( + service, + embedded_services::service::ServiceRunner::new(service, RunnerCreationToken::new()), + )) } /// Query clock capabilities. Analogous to ACPI TAD's _GRT method. @@ -263,17 +265,6 @@ impl<'hw> Service<'hw> { } } - pub(crate) async fn run_service(&'hw self) -> ! { - loop { - embassy_futures::select::select3( - self.handle_power_source_updates(), - self.handle_timer(AcpiTimerId::AcPower), - self.handle_timer(AcpiTimerId::DcPower), - ) - .await; - } - } - async fn handle_power_source_updates(&'hw self) -> ! { loop { let new_power_source = self.power_source_signal.wait().await; @@ -311,6 +302,22 @@ impl<'hw> Service<'hw> { } } +embedded_services::impl_runner_creation_token!(RunnerCreationToken); + +impl<'hw> embedded_services::service::RunnableService<'hw> for Service<'hw> { + type RunnerCreationToken = RunnerCreationToken; + async fn run(&'hw self, _: Self::RunnerCreationToken) -> embedded_services::Never { + loop { + embassy_futures::select::select3( + self.handle_power_source_updates(), + self.handle_timer(AcpiTimerId::AcPower), + self.handle_timer(AcpiTimerId::DcPower), + ) + .await; + } + } +} + impl<'hw> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'hw> { type RequestType = AcpiTimeAlarmRequest; type ResultType = AcpiTimeAlarmResult; diff --git a/time-alarm-service/src/task.rs b/time-alarm-service/src/task.rs deleted file mode 100644 index a45fdf62..00000000 --- a/time-alarm-service/src/task.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::Service; -use embedded_services::info; - -/// Call this from a dedicated async task. Must be called exactly once on each service instance. -/// Note that on-device, 'hw must be 'static. We're generic over 'hw to enable some test scenarios leveraging tokio and mocks. -pub async fn run_service<'hw>(service: &'hw Service<'hw>) -> ! { - info!("Starting time-alarm service task"); - service.run_service().await -} diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs index dc3d43d8..6b3b71e0 100644 --- a/time-alarm-service/tests/tad_test.rs +++ b/time-alarm-service/tests/tad_test.rs @@ -25,7 +25,7 @@ mod test { let mut clock = MockDatetimeClock::new_running(); let storage = OnceLock::new(); - let service = time_alarm_service::Service::init( + let (service, runner) = time_alarm_service::Service::init( &storage, &mut clock, &mut tz_storage, @@ -45,7 +45,7 @@ mod test { // return !, so we should go until the test arm completes and then shut down. // tokio::select! { - _ = time_alarm_service::task::run_service(service) => unreachable!("time alarm service task finished unexpectedly"), + _ = runner.run() => unreachable!("time alarm service task finished unexpectedly"), _ = async { let delay_secs = 2; let begin = service.get_real_time().unwrap(); @@ -74,7 +74,7 @@ mod test { .unwrap(); let storage = OnceLock::new(); - let service = time_alarm_service::Service::init( + let (service, runner) = time_alarm_service::Service::init( &storage, &mut clock, &mut tz_storage, @@ -87,7 +87,7 @@ mod test { .unwrap(); tokio::select! { - _ = time_alarm_service::task::run_service(service) => unreachable!("time alarm service task finished unexpectedly"), + _ = runner.run() => unreachable!("time alarm service task finished unexpectedly"), _ = async { // Clock is paused, so time shouldn't advance unless we set it. let begin = service.get_real_time().unwrap(); From 33b6b63542b99d4cfddc48dcbbfba1c8866fcea9 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Thu, 19 Feb 2026 17:11:39 -0800 Subject: [PATCH 2/5] variant that uses traits instead of structural typing for init fn --- embedded-service/src/service.rs | 18 +++- espi-service/src/espi_service.rs | 30 +++--- examples/rt685s-evk/src/bin/time_alarm.rs | 14 +-- time-alarm-service/src/lib.rs | 107 ++++++++++++---------- time-alarm-service/tests/tad_test.rs | 32 ++++--- 5 files changed, 120 insertions(+), 81 deletions(-) diff --git a/embedded-service/src/service.rs b/embedded-service/src/service.rs index 3b9b6178..557a4635 100644 --- a/embedded-service/src/service.rs +++ b/embedded-service/src/service.rs @@ -3,7 +3,7 @@ /// A trait for a service that can be run on the EC. /// Implementations of RunnableService should have an init() function to construct the service that /// returns a Runner, which the user is expected to spawn a task for. -pub trait RunnableService<'hw> { +pub trait RunnableService<'hw>: Sized { /// A token type used to restrict users from spawning more than one service runner. Services will generally /// define this as a zero-sized type and only provide a constructor for it that is private to the service module, /// which prevents users from constructing their own tokens and spawning multiple runners. @@ -15,6 +15,21 @@ pub trait RunnableService<'hw> { &'hw self, _creation_token: Self::RunnerCreationToken, ) -> impl core::future::Future + 'hw; + + // ##### NOTE - below this line is only needed to get typesafety for spawn_service!(), which we could do without ##### + + /// The error type that your `init` function can return on failure. + type ErrorType; + + /// Any initialization parameters that your service needs to run. + type InitParams; + + /// Initializes an instance of the service in the provided OnceLock and returns a reference to the service and + /// a runner that can be used to run the service. + fn init( + storage: &'hw embassy_sync::once_lock::OnceLock, + params: Self::InitParams, + ) -> impl core::future::Future), Self::ErrorType>>; } /// A handle that must be passed to a spawned task and `.run().await`'d to drive the service. @@ -107,6 +122,7 @@ pub use impl_runner_creation_token; macro_rules! spawn_service { ($spawner:expr, [ $($service_ty:tt)* ], $($init_args:expr),* $(,)?) => { { + use embedded_services::service::RunnableService; static SERVICE: embassy_sync::once_lock::OnceLock<$($service_ty)*> = embassy_sync::once_lock::OnceLock::new(); match <$($service_ty)*>::init( &SERVICE, diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 872816a2..122856c4 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -78,8 +78,15 @@ pub struct Service { embedded_services::impl_runner_creation_token!(RunnerCreationToken); +pub struct ServiceInitParams { + pub espi: espi::Espi<'static>, + pub relay_handler: RelayHandler, +} + impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_services::service::RunnableService<'hw> for Service +where + 'hw: 'static, // TODO we should be able to relax this constraint when we remove the dependency on the comms service { type RunnerCreationToken = RunnerCreationToken; async fn run(&'hw self, _: Self::RunnerCreationToken) -> embedded_services::Never { @@ -101,26 +108,22 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_s } } } -} -impl Service { // TODO a lot of the input lifetimes here have to be static because we have a dependency on the comms system, which requires // that everything that talks over it is 'static. Once we eliminate that dependency, we should be able to relax these lifetimes. - pub async fn init( - service_storage: &'static OnceLock, - mut espi: espi::Espi<'static>, - relay_handler: RelayHandler, - ) -> Result<(&'static Self, embedded_services::service::ServiceRunner<'static, Self>), core::convert::Infallible> - { - espi.wait_for_plat_reset().await; + async fn init( + service_storage: &'hw OnceLock, + mut params: Self::InitParams, + ) -> Result<(&'hw Self, embedded_services::service::ServiceRunner<'hw, Self>), core::convert::Infallible> { + params.espi.wait_for_plat_reset().await; let service = service_storage.get_or_init(|| Service { endpoint: DEPRECATED_comms::Endpoint::uninit(DEPRECATED_comms::EndpointID::External( DEPRECATED_comms::External::Host, )), - espi: Mutex::new(espi), + espi: Mutex::new(params.espi), host_tx_queue: Channel::new(), - relay_handler, + relay_handler: params.relay_handler, }); DEPRECATED_comms::register_endpoint(service, &service.endpoint) @@ -133,6 +136,11 @@ impl Service; +} + +impl Service { // TODO The notification system was not actually used, so this is currently dead code. // We need to implement some interface for triggering notifications from other subsystems, and it may do something like this: // diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 80c50cdd..7a87c395 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -26,12 +26,14 @@ async fn main(spawner: embassy_executor::Spawner) { let time_service = embedded_services::spawn_service!( spawner, [time_alarm_service::Service<'static>], - dt_clock, - tz, - ac_expiration, - ac_policy, - dc_expiration, - dc_policy + time_alarm_service::ServiceInitParams { + backing_clock: dt_clock, + tz_storage: tz, + ac_expiration_storage: ac_expiration, + ac_policy_storage: ac_policy, + dc_expiration_storage: dc_expiration, + dc_policy_storage: dc_policy + } ) .expect("Failed to spawn time alarm service"); diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index ee1c0e9d..ebdf73a4 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -115,56 +115,6 @@ pub struct Service<'hw> { } impl<'hw> Service<'hw> { - pub async fn init( - service_storage: &'hw OnceLock>, - backing_clock: &'hw mut impl DatetimeClock, - tz_storage: &'hw mut dyn NvramStorage<'hw, u32>, - ac_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, - ac_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, - dc_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, - dc_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, - ) -> Result<(&'hw Self, embedded_services::service::ServiceRunner<'hw, Self>), DatetimeClockError> { - info!("Starting time-alarm service task"); - - let service = service_storage.get_or_init(|| Service { - clock_state: Mutex::new(RefCell::new(ClockState { - datetime_clock: backing_clock, - tz_data: TimeZoneData::new(tz_storage), - })), - power_source_signal: Signal::new(), - timers: Timers::new( - ac_expiration_storage, - ac_policy_storage, - dc_expiration_storage, - dc_policy_storage, - ), - capabilities: { - // TODO [CONFIG] We could consider making some of these user-configurable, e.g. if we want to support devices that don't have a battery - let mut caps = TimeAlarmDeviceCapabilities(0); - caps.set_ac_wake_implemented(true); - caps.set_dc_wake_implemented(true); - caps.set_realtime_implemented(true); - caps.set_realtime_accuracy_in_milliseconds(false); - caps.set_get_wake_status_supported(true); - caps.set_ac_s4_wake_supported(true); - caps.set_ac_s5_wake_supported(true); - caps.set_dc_s4_wake_supported(true); - caps.set_dc_s5_wake_supported(true); - caps - }, - }); - - // TODO [POWER_SOURCE] we need to subscribe to messages that tell us if we're on AC or DC power so we can decide which alarms to trigger, but those notifications are not yet implemented - revisit when they are. - // TODO [POWER_SOURCE] if it's possible to learn which power source is active at init time, we should set that one active rather than defaulting to the AC timer. - service.timers.ac_timer.start(&service.clock_state, true)?; - service.timers.dc_timer.start(&service.clock_state, false)?; - - Ok(( - service, - embedded_services::service::ServiceRunner::new(service, RunnerCreationToken::new()), - )) - } - /// Query clock capabilities. Analogous to ACPI TAD's _GRT method. pub fn get_capabilities(&self) -> TimeAlarmDeviceCapabilities { self.capabilities @@ -304,6 +254,15 @@ impl<'hw> Service<'hw> { embedded_services::impl_runner_creation_token!(RunnerCreationToken); +pub struct ServiceInitParams<'hw> { + pub backing_clock: &'hw mut dyn DatetimeClock, + pub tz_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub ac_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub ac_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub dc_expiration_storage: &'hw mut dyn NvramStorage<'hw, u32>, + pub dc_policy_storage: &'hw mut dyn NvramStorage<'hw, u32>, +} + impl<'hw> embedded_services::service::RunnableService<'hw> for Service<'hw> { type RunnerCreationToken = RunnerCreationToken; async fn run(&'hw self, _: Self::RunnerCreationToken) -> embedded_services::Never { @@ -316,6 +275,54 @@ impl<'hw> embedded_services::service::RunnableService<'hw> for Service<'hw> { .await; } } + + type ErrorType = DatetimeClockError; + type InitParams = ServiceInitParams<'hw>; + + async fn init( + service_storage: &'hw OnceLock>, + init_params: Self::InitParams, + ) -> Result<(&'hw Self, embedded_services::service::ServiceRunner<'hw, Self>), DatetimeClockError> { + info!("Starting time-alarm service task"); + + let service = service_storage.get_or_init(|| Service { + clock_state: Mutex::new(RefCell::new(ClockState { + datetime_clock: init_params.backing_clock, + tz_data: TimeZoneData::new(init_params.tz_storage), + })), + power_source_signal: Signal::new(), + timers: Timers::new( + init_params.ac_expiration_storage, + init_params.ac_policy_storage, + init_params.dc_expiration_storage, + init_params.dc_policy_storage, + ), + capabilities: { + // TODO [CONFIG] We could consider making some of these user-configurable, e.g. if we want to support devices that don't have a battery + let mut caps = TimeAlarmDeviceCapabilities(0); + caps.set_ac_wake_implemented(true); + caps.set_dc_wake_implemented(true); + caps.set_realtime_implemented(true); + caps.set_realtime_accuracy_in_milliseconds(false); + caps.set_get_wake_status_supported(true); + caps.set_ac_s4_wake_supported(true); + caps.set_ac_s5_wake_supported(true); + caps.set_dc_s4_wake_supported(true); + caps.set_dc_s5_wake_supported(true); + caps + }, + }); + + // TODO [POWER_SOURCE] we need to subscribe to messages that tell us if we're on AC or DC power so we can decide which alarms to trigger, but those notifications are not yet implemented - revisit when they are. + // TODO [POWER_SOURCE] if it's possible to learn which power source is active at init time, we should set that one active rather than defaulting to the AC timer. + service.timers.ac_timer.start(&service.clock_state, true)?; + service.timers.dc_timer.start(&service.clock_state, false)?; + + Ok(( + service, + embedded_services::service::ServiceRunner::new(service, RunnerCreationToken::new()), + )) + } } impl<'hw> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'hw> { diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs index 6b3b71e0..479c1a12 100644 --- a/time-alarm-service/tests/tad_test.rs +++ b/time-alarm-service/tests/tad_test.rs @@ -9,6 +9,7 @@ mod test { use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; use embedded_mcu_hal::time::{Datetime, DatetimeClock}; + use embedded_services::service::RunnableService; use time_alarm_service_messages as msg; @@ -23,16 +24,18 @@ mod test { let mut dc_pol_storage = MockNvramStorage::new(0); let mut clock = MockDatetimeClock::new_running(); - let storage = OnceLock::new(); + let (service, runner) = time_alarm_service::Service::init( &storage, - &mut clock, - &mut tz_storage, - &mut ac_exp_storage, - &mut ac_pol_storage, - &mut dc_exp_storage, - &mut dc_pol_storage, + time_alarm_service::ServiceInitParams { + backing_clock: &mut clock, + tz_storage: &mut tz_storage, + ac_expiration_storage: &mut ac_exp_storage, + ac_policy_storage: &mut ac_pol_storage, + dc_expiration_storage: &mut dc_exp_storage, + dc_policy_storage: &mut dc_pol_storage, + }, ) .await .unwrap(); @@ -74,14 +77,17 @@ mod test { .unwrap(); let storage = OnceLock::new(); + let (service, runner) = time_alarm_service::Service::init( &storage, - &mut clock, - &mut tz_storage, - &mut ac_exp_storage, - &mut ac_pol_storage, - &mut dc_exp_storage, - &mut dc_pol_storage, + time_alarm_service::ServiceInitParams { + backing_clock: &mut clock, + tz_storage: &mut tz_storage, + ac_expiration_storage: &mut ac_exp_storage, + ac_policy_storage: &mut ac_pol_storage, + dc_expiration_storage: &mut dc_exp_storage, + dc_policy_storage: &mut dc_pol_storage, + }, ) .await .unwrap(); From cab712840c99e367a4994aeb972f4d040b9bc5a5 Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 20 Feb 2026 11:17:08 -0800 Subject: [PATCH 3/5] cleanup --- embedded-service/src/service.rs | 57 ++++++++--------------- examples/rt685s-evk/src/bin/time_alarm.rs | 2 +- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/embedded-service/src/service.rs b/embedded-service/src/service.rs index 557a4635..77842aeb 100644 --- a/embedded-service/src/service.rs +++ b/embedded-service/src/service.rs @@ -33,7 +33,7 @@ pub trait RunnableService<'hw>: Sized { } /// A handle that must be passed to a spawned task and `.run().await`'d to drive the service. -/// Dropping this without calling `runner.run().await` means the service will not process events +#[must_use = "Dropping the ServiceRunner without calling .run().await means the service will not process events"] pub struct ServiceRunner<'hw, T: RunnableService<'hw>> { service: &'hw T, creation_token: T::RunnerCreationToken, // This token is used to ensure that only the service can create a runner for itself. It's probably a zero-sized type. @@ -89,61 +89,44 @@ pub use impl_runner_creation_token; /// This macro handles the boilerplate of: /// 1. Creating a `static` [`OnceLock`](embassy_sync::once_lock::OnceLock) to hold the service /// 2. Calling the service's `init()` method -/// 3. Defining an [`embassy_executor::task`] to run the service +/// 3. Defining an embassy_executor::task to run the service /// 4. Spawning the task on the provided executor /// /// Returns a Result where Error is the error type of $service_ty::init(). /// -/// Note that for a service to be supported, it must have the following properties: // TODO figure out if this should be a trait. Would require a single associated-type arg rather than letting each service define its own init list though... -/// 1. Implements the RunnableService trait -/// 2. Has an init() function with the following properties: -/// i. Takes as its first argument a &OnceLock -/// ii. Returns a Result<(reference-to-service, service-runner), Error> where the service-runner -/// is an instance of RunnableService. -/// /// Arguments /// -/// - spawner: An [`embassy_executor::Spawner`]. -/// - service_ty: The service type, wrapped in brackets to allow generic arguments -/// (e.g. `[my_crate::Service<'static>]`). -/// - init_args: The arguments to pass to `Service::init()`, excluding the `OnceLock` argument, which is codegenned. +/// - spawner: An embassy_executor::Spawner. +/// - service_ty: The service type that implements RunnableService that you want to creat and run. +/// - init_arg: The init argument type to pass to `Service::init()` /// /// Example: /// /// ```ignore /// let time_service = embedded_services::spawn_service!( -/// time_alarm_task, /// spawner, -/// [time_alarm_service::Service<'static>], -/// dt_clock, tz, ac_expiration, ac_policy, dc_expiration, dc_policy +/// time_alarm_service::Service<'static>, +/// time_alarm_service::ServiceInitParams { dt_clock, tz, ac_expiration, ac_policy, dc_expiration, dc_policy } /// ).expect("failed to initialize time_alarm service"); /// ``` #[macro_export] macro_rules! spawn_service { - ($spawner:expr, [ $($service_ty:tt)* ], $($init_args:expr),* $(,)?) => { - { - use embedded_services::service::RunnableService; - static SERVICE: embassy_sync::once_lock::OnceLock<$($service_ty)*> = embassy_sync::once_lock::OnceLock::new(); - match <$($service_ty)*>::init( - &SERVICE, - $($init_args),* - ) - .await { - Ok((service_ref, runner)) => { - #[embassy_executor::task] - async fn service_task_fn( - runner: $crate::service::ServiceRunner<'static, $($service_ty)*>, - ) { - runner.run().await; - } + ($spawner:expr, $service_ty:ty, $init_arg:expr) => {{ + use embedded_services::service::RunnableService; + static SERVICE: embassy_sync::once_lock::OnceLock<$service_ty> = embassy_sync::once_lock::OnceLock::new(); + match <$service_ty>::init(&SERVICE, $init_arg).await { + Ok((service_ref, runner)) => { + #[embassy_executor::task] + async fn service_task_fn(runner: $crate::service::ServiceRunner<'static, $service_ty>) { + runner.run().await; + } - $spawner.must_spawn(service_task_fn(runner)); - Ok(service_ref) - }, - Err(e) => Err(e) + $spawner.must_spawn(service_task_fn(runner)); + Ok(service_ref) } + Err(e) => Err(e), } - }; + }}; } pub use spawn_service; diff --git a/examples/rt685s-evk/src/bin/time_alarm.rs b/examples/rt685s-evk/src/bin/time_alarm.rs index 7a87c395..161017f1 100644 --- a/examples/rt685s-evk/src/bin/time_alarm.rs +++ b/examples/rt685s-evk/src/bin/time_alarm.rs @@ -25,7 +25,7 @@ async fn main(spawner: embassy_executor::Spawner) { let time_service = embedded_services::spawn_service!( spawner, - [time_alarm_service::Service<'static>], + time_alarm_service::Service<'static>, time_alarm_service::ServiceInitParams { backing_clock: dt_clock, tz_storage: tz, From df240bb816c3188d8738b5f0d4fe6d3d14ec0b5a Mon Sep 17 00:00:00 2001 From: Billy Price Date: Fri, 20 Feb 2026 11:31:40 -0800 Subject: [PATCH 4/5] cleanup --- embedded-service/src/service.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/embedded-service/src/service.rs b/embedded-service/src/service.rs index 77842aeb..81c120fe 100644 --- a/embedded-service/src/service.rs +++ b/embedded-service/src/service.rs @@ -10,20 +10,18 @@ pub trait RunnableService<'hw>: Sized { /// Most services should consider using the `impl_runner_creation_token!` macro to do this automatically. type RunnerCreationToken; - /// Run the service event loop. This future never completes. - fn run( - &'hw self, - _creation_token: Self::RunnerCreationToken, - ) -> impl core::future::Future + 'hw; - - // ##### NOTE - below this line is only needed to get typesafety for spawn_service!(), which we could do without ##### - /// The error type that your `init` function can return on failure. type ErrorType; /// Any initialization parameters that your service needs to run. type InitParams; + /// Run the service event loop. This future never completes. + fn run( + &'hw self, + _creation_token: Self::RunnerCreationToken, + ) -> impl core::future::Future + 'hw; + /// Initializes an instance of the service in the provided OnceLock and returns a reference to the service and /// a runner that can be used to run the service. fn init( @@ -97,7 +95,7 @@ pub use impl_runner_creation_token; /// Arguments /// /// - spawner: An embassy_executor::Spawner. -/// - service_ty: The service type that implements RunnableService that you want to creat and run. +/// - service_ty: The service type that implements RunnableService that you want to create and run. /// - init_arg: The init argument type to pass to `Service::init()` /// /// Example: @@ -112,7 +110,7 @@ pub use impl_runner_creation_token; #[macro_export] macro_rules! spawn_service { ($spawner:expr, $service_ty:ty, $init_arg:expr) => {{ - use embedded_services::service::RunnableService; + use $crate::service::RunnableService; static SERVICE: embassy_sync::once_lock::OnceLock<$service_ty> = embassy_sync::once_lock::OnceLock::new(); match <$service_ty>::init(&SERVICE, $init_arg).await { Ok((service_ref, runner)) => { From 0cc02cd1a4bfcb88cd951a0c2d1018e6171d5bda Mon Sep 17 00:00:00 2001 From: Billy Price Date: Tue, 24 Feb 2026 13:15:43 -0800 Subject: [PATCH 5/5] Updates per RFC feedback: - Remove run() from the RunnableService trait and migrate it to a ServiceRunner trait to enable services with &mut methods to be placed in mutexes - Drop common implementation of ServiceRunner, creation token becuase it's incompatible with the mutex-friendly approach - Update eSPI and TAD service to reflect changes --- embedded-service/src/service.rs | 77 ++++++---------------------- espi-service/src/espi_service.rs | 65 ++++++++++++----------- time-alarm-service/src/lib.rs | 39 +++++++------- time-alarm-service/tests/tad_test.rs | 2 +- 4 files changed, 75 insertions(+), 108 deletions(-) diff --git a/embedded-service/src/service.rs b/embedded-service/src/service.rs index 81c120fe..704573fb 100644 --- a/embedded-service/src/service.rs +++ b/embedded-service/src/service.rs @@ -8,7 +8,7 @@ pub trait RunnableService<'hw>: Sized { /// define this as a zero-sized type and only provide a constructor for it that is private to the service module, /// which prevents users from constructing their own tokens and spawning multiple runners. /// Most services should consider using the `impl_runner_creation_token!` macro to do this automatically. - type RunnerCreationToken; + type Runner: ServiceRunner<'hw>; /// The error type that your `init` function can return on failure. type ErrorType; @@ -16,72 +16,28 @@ pub trait RunnableService<'hw>: Sized { /// Any initialization parameters that your service needs to run. type InitParams; - /// Run the service event loop. This future never completes. - fn run( - &'hw self, - _creation_token: Self::RunnerCreationToken, - ) -> impl core::future::Future + 'hw; - /// Initializes an instance of the service in the provided OnceLock and returns a reference to the service and /// a runner that can be used to run the service. fn init( - storage: &'hw embassy_sync::once_lock::OnceLock, + storage: &'hw embassy_sync::once_lock::OnceLock, // TODO could be resources? params: Self::InitParams, - ) -> impl core::future::Future), Self::ErrorType>>; + ) -> impl core::future::Future>; } -/// A handle that must be passed to a spawned task and `.run().await`'d to drive the service. -#[must_use = "Dropping the ServiceRunner without calling .run().await means the service will not process events"] -pub struct ServiceRunner<'hw, T: RunnableService<'hw>> { - service: &'hw T, - creation_token: T::RunnerCreationToken, // This token is used to ensure that only the service can create a runner for itself. It's probably a zero-sized type. -} - -impl<'hw, T: RunnableService<'hw>> ServiceRunner<'hw, T> { - /// Runs the service event loop. This future never completes. - pub async fn run(self) -> crate::Never { - self.service.run(self.creation_token).await - } - - /// Constructs a new service runner. This is something the service will do in its init function; users of - /// the service should not need to call this directly. - pub fn new(service: &'hw T, token: T::RunnerCreationToken) -> Self { - Self { - service, - creation_token: token, - } - } -} - -/// Generates a default implementation of a runner creation token for a service. This token is used to ensure that -/// only the service can create a runner for itself, and therefore it can control the number of tasks that a user is -/// allowed to spawn to run the service (e.g. if the service is not designed to be run by multiple tasks, it can use -/// this token to prevent that). -/// -/// Most services will want to use this macro to generate a simple zero-sized token type - it needs to be a macro invoked -/// in the service module rather than a generic type in this module because the constructor needs to be private to the -/// service module to prevent users from constructing their own tokens and spawning multiple runners. -/// -/// Arguments: -/// - token_name: The name of the token type to generate. -#[macro_export] -macro_rules! impl_runner_creation_token { - ($token_name:ident) => { - /// A token type used to restrict users from spawning more than one service runner. - pub struct $token_name { - _private: (), - } - - impl $token_name { - fn new() -> Self { - Self { _private: () } - } - } - }; +/// A trait for a run handle used to execute a service's event loop. This is returned by RunnableService::init() +/// and the user is expected to call its run() method in an embassy task (or similar parallel execution context +/// on other async runtimes). +pub trait ServiceRunner<'hw> { + /// Run the service event loop. This future never completes. + fn run(self) -> impl core::future::Future + 'hw; + // TODO: Do we want to take &mut self instead of consuming self? I think the difference is that it allows for the possibility of + // the user select!()ing over the ServiceRunner and something else, then having that other thing complete and bailing + // out of execution. In the consume-self version, the user can't restart afterward, but in the &mut self version they could + // potentially restart the runner. It's not clear to me if we have any use cases for the 'restartable runner' version, and if + // we don't then the consume-self version more clearly telegraphs the fact that the runner is not meant to be restarted or + // reused after it's started and lets the implementor care less about drop safety on the future } -pub use impl_runner_creation_token; - /// Initializes a service, creates an embassy task to run it, and spawns that task. /// /// This macro handles the boilerplate of: @@ -111,11 +67,12 @@ pub use impl_runner_creation_token; macro_rules! spawn_service { ($spawner:expr, $service_ty:ty, $init_arg:expr) => {{ use $crate::service::RunnableService; + use $crate::service::ServiceRunner; static SERVICE: embassy_sync::once_lock::OnceLock<$service_ty> = embassy_sync::once_lock::OnceLock::new(); match <$service_ty>::init(&SERVICE, $init_arg).await { Ok((service_ref, runner)) => { #[embassy_executor::task] - async fn service_task_fn(runner: $crate::service::ServiceRunner<'static, $service_ty>) { + async fn service_task_fn(runner: <$service_ty as $crate::service::RunnableService<'static>>::Runner) { runner.run().await; } diff --git a/espi-service/src/espi_service.rs b/espi-service/src/espi_service.rs index 122856c4..e6bbea7c 100644 --- a/espi-service/src/espi_service.rs +++ b/espi-service/src/espi_service.rs @@ -76,45 +76,34 @@ pub struct Service { ///////// COMMON FUNCTIONS /////////// -embedded_services::impl_runner_creation_token!(RunnerCreationToken); +pub struct Runner<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> { + service: &'hw Service, +} + +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_services::service::ServiceRunner<'hw> + for Runner<'hw, RelayHandler> +{ + async fn run(self) -> embedded_services::Never { + self.service.run().await + } +} pub struct ServiceInitParams { pub espi: espi::Espi<'static>, pub relay_handler: RelayHandler, } -impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_services::service::RunnableService<'hw> - for Service +impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler + 'hw> + embedded_services::service::RunnableService<'hw> for Service where 'hw: 'static, // TODO we should be able to relax this constraint when we remove the dependency on the comms service { - type RunnerCreationToken = RunnerCreationToken; - async fn run(&'hw self, _: Self::RunnerCreationToken) -> embedded_services::Never { - let mut espi = self.espi.lock().await; - loop { - let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await; - - match event { - embassy_futures::select::Either::First(controller_event) => { - self.process_controller_event(&mut espi, controller_event) - .await - .unwrap_or_else(|e| { - error!("Critical error processing eSPI controller event: {:?}", e); - }); - } - embassy_futures::select::Either::Second(host_msg) => { - self.process_response_to_host_routing(&mut espi, host_msg).await - } - } - } - } - // TODO a lot of the input lifetimes here have to be static because we have a dependency on the comms system, which requires // that everything that talks over it is 'static. Once we eliminate that dependency, we should be able to relax these lifetimes. async fn init( service_storage: &'hw OnceLock, mut params: Self::InitParams, - ) -> Result<(&'hw Self, embedded_services::service::ServiceRunner<'hw, Self>), core::convert::Infallible> { + ) -> Result<(&'hw Self, Runner<'hw, RelayHandler>), core::convert::Infallible> { params.espi.wait_for_plat_reset().await; let service = service_storage.get_or_init(|| Service { @@ -130,14 +119,12 @@ where .await .unwrap(); - Ok(( - service, - embedded_services::service::ServiceRunner::new(service, RunnerCreationToken::new()), - )) + Ok((service, Runner { service })) } type ErrorType = core::convert::Infallible; type InitParams = ServiceInitParams; + type Runner = Runner<'hw, RelayHandler>; } impl Service { @@ -149,6 +136,26 @@ impl Service embedded_services::Never { + let mut espi = self.espi.lock().await; + loop { + let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await; + + match event { + embassy_futures::select::Either::First(controller_event) => { + self.process_controller_event(&mut espi, controller_event) + .await + .unwrap_or_else(|e| { + error!("Critical error processing eSPI controller event: {:?}", e); + }); + } + embassy_futures::select::Either::Second(host_msg) => { + self.process_response_to_host_routing(&mut espi, host_msg).await + } + } + } + } + fn write_to_hw(&self, espi: &mut espi::Espi<'static>, packet: &[u8]) -> Result<(), embassy_imxrt::espi::Error> { // Send packet via your transport medium // SAFETY: Safe as the access to espi is protected by a mut reference. diff --git a/time-alarm-service/src/lib.rs b/time-alarm-service/src/lib.rs index ebdf73a4..e8af5de1 100644 --- a/time-alarm-service/src/lib.rs +++ b/time-alarm-service/src/lib.rs @@ -252,7 +252,24 @@ impl<'hw> Service<'hw> { } } -embedded_services::impl_runner_creation_token!(RunnerCreationToken); +pub struct Runner<'hw> { + service: &'hw Service<'hw>, +} + +impl<'hw> embedded_services::service::ServiceRunner<'hw> for Runner<'hw> { + fn run(self) -> impl core::future::Future + 'hw { + async move { + loop { + embassy_futures::select::select3( + self.service.handle_power_source_updates(), + self.service.handle_timer(AcpiTimerId::AcPower), + self.service.handle_timer(AcpiTimerId::DcPower), + ) + .await; + } + } + } +} pub struct ServiceInitParams<'hw> { pub backing_clock: &'hw mut dyn DatetimeClock, @@ -264,25 +281,14 @@ pub struct ServiceInitParams<'hw> { } impl<'hw> embedded_services::service::RunnableService<'hw> for Service<'hw> { - type RunnerCreationToken = RunnerCreationToken; - async fn run(&'hw self, _: Self::RunnerCreationToken) -> embedded_services::Never { - loop { - embassy_futures::select::select3( - self.handle_power_source_updates(), - self.handle_timer(AcpiTimerId::AcPower), - self.handle_timer(AcpiTimerId::DcPower), - ) - .await; - } - } - + type Runner = Runner<'hw>; type ErrorType = DatetimeClockError; type InitParams = ServiceInitParams<'hw>; async fn init( service_storage: &'hw OnceLock>, init_params: Self::InitParams, - ) -> Result<(&'hw Self, embedded_services::service::ServiceRunner<'hw, Self>), DatetimeClockError> { + ) -> Result<(&'hw Self, Runner<'hw>), DatetimeClockError> { info!("Starting time-alarm service task"); let service = service_storage.get_or_init(|| Service { @@ -318,10 +324,7 @@ impl<'hw> embedded_services::service::RunnableService<'hw> for Service<'hw> { service.timers.ac_timer.start(&service.clock_state, true)?; service.timers.dc_timer.start(&service.clock_state, false)?; - Ok(( - service, - embedded_services::service::ServiceRunner::new(service, RunnerCreationToken::new()), - )) + Ok((service, Runner { service })) } } diff --git a/time-alarm-service/tests/tad_test.rs b/time-alarm-service/tests/tad_test.rs index 479c1a12..14cfea16 100644 --- a/time-alarm-service/tests/tad_test.rs +++ b/time-alarm-service/tests/tad_test.rs @@ -9,7 +9,7 @@ mod test { use embassy_sync::once_lock::OnceLock; use embassy_time::Timer; use embedded_mcu_hal::time::{Datetime, DatetimeClock}; - use embedded_services::service::RunnableService; + use embedded_services::service::{RunnableService, ServiceRunner}; use time_alarm_service_messages as msg;