diff --git a/embedded-service/src/event.rs b/embedded-service/src/event.rs index 416a92c5..dfabd3d8 100644 --- a/embedded-service/src/event.rs +++ b/embedded-service/src/event.rs @@ -1,5 +1,7 @@ //! Common traits for event senders and receivers +use core::marker::PhantomData; + use embassy_sync::channel::{DynamicReceiver, DynamicSender}; /// Common event sender trait @@ -41,3 +43,42 @@ impl Receiver for DynamicReceiver<'_, E> { self.receive() } } + +/// A sender that discards all events sent to it. +pub struct NoopSender; + +impl Sender for NoopSender { + fn try_send(&mut self, _event: E) -> Option<()> { + Some(()) + } + + async fn send(&mut self, _event: E) {} +} + +/// Applies a function on events before passing them to the wrapped sender +pub struct MapSender, F: FnMut(I) -> O> { + sender: S, + map_fn: F, + _phantom: PhantomData<(I, O)>, +} + +impl, F: FnMut(I) -> O> MapSender { + /// Create a new self + pub fn new(sender: S, map_fn: F) -> Self { + Self { + sender, + map_fn, + _phantom: PhantomData, + } + } +} + +impl, F: FnMut(I) -> O> Sender for MapSender { + fn try_send(&mut self, event: I) -> Option<()> { + self.sender.try_send((self.map_fn)(event)) + } + + fn send(&mut self, event: I) -> impl Future { + self.sender.send((self.map_fn)(event)) + } +} diff --git a/embedded-service/src/lib.rs b/embedded-service/src/lib.rs index c53aba53..e62562e3 100644 --- a/embedded-service/src/lib.rs +++ b/embedded-service/src/lib.rs @@ -21,6 +21,7 @@ pub mod hid; pub mod init; pub mod ipc; pub mod keyboard; +pub mod named; pub mod relay; pub mod sync; diff --git a/embedded-service/src/named.rs b/embedded-service/src/named.rs new file mode 100644 index 00000000..feebe699 --- /dev/null +++ b/embedded-service/src/named.rs @@ -0,0 +1,7 @@ +//! Traits for things that have names. + +/// Trait for anything that has a name. +pub trait Named { + /// Return name + fn name(&self) -> &'static str; +} diff --git a/examples/rt685s-evk/src/bin/type_c.rs b/examples/rt685s-evk/src/bin/type_c.rs index 5232dcf0..2d7ac872 100644 --- a/examples/rt685s-evk/src/bin/type_c.rs +++ b/examples/rt685s-evk/src/bin/type_c.rs @@ -15,11 +15,13 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion, HostToken}; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; @@ -64,6 +66,11 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn pd_controller_task(controller: &'static Wrapper<'static>) { loop { @@ -80,8 +87,8 @@ async fn interrupt_task(mut int_in: Input<'static>, mut interrupt: Interrupt<'st #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -204,13 +211,14 @@ async fn main(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -233,7 +241,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/rt685s-evk/src/bin/type_c_cfu.rs b/examples/rt685s-evk/src/bin/type_c_cfu.rs index f8ab5c20..20f5b65d 100644 --- a/examples/rt685s-evk/src/bin/type_c_cfu.rs +++ b/examples/rt685s-evk/src/bin/type_c_cfu.rs @@ -18,11 +18,13 @@ use embassy_time::Timer; use embassy_time::{self as _, Delay}; use embedded_cfu_protocol::protocol_definitions::*; use embedded_cfu_protocol::protocol_definitions::{FwUpdateOffer, FwUpdateOfferResponse, FwVersion}; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_services::{error, info}; use embedded_usb_pd::GlobalPortId; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use tps6699x::asynchronous::embassy as tps6699x; use type_c_service::driver::tps6699x::{self as tps6699x_drv}; @@ -62,6 +64,11 @@ type Wrapper<'a> = ControllerWrapper< type Controller<'a> = tps6699x::controller::Controller>; type Interrupt<'a> = tps6699x::Interrupt<'a, GlobalRawMutex, BusDevice<'a>>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + const NUM_PD_CONTROLLERS: usize = 1; const CONTROLLER0_ID: ControllerId = ControllerId(0); const CONTROLLER0_CFU_ID: ComponentId = 0x12; @@ -164,8 +171,8 @@ async fn fw_update_task() { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -282,13 +289,14 @@ async fn main(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy, &wrapper.ports[1].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -321,7 +329,7 @@ async fn main(spawner: Spawner) { info!("Spawining power policy task"); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper.ports[0].proxy, &wrapper.ports[1].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/src/bin/power_policy.rs b/examples/std/src/bin/power_policy.rs index 40e8a292..6cf7e51f 100644 --- a/examples/std/src/bin/power_policy.rs +++ b/examples/std/src/bin/power_policy.rs @@ -5,16 +5,21 @@ use embassy_sync::{ mutex::Mutex, }; use embassy_time::{self as _, Timer}; -use embedded_services::GlobalRawMutex; +use embedded_services::{GlobalRawMutex, event::NoopSender, named::Named}; use log::*; use power_policy_interface::psu::{Error, Psu}; use power_policy_interface::{ capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, psu, }; -use power_policy_service::psu::EventReceivers; +use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; use static_cell::StaticCell; +type ServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + const LOW_POWER: PowerCapability = PowerCapability { voltage_mv: 5000, current_ma: 1500, @@ -96,7 +101,9 @@ impl Psu for ExampleDevice<'_> { fn state_mut(&mut self) -> &mut psu::State { &mut self.state } +} +impl Named for ExampleDevice<'_> { fn name(&self) -> &'static str { self.name } @@ -131,19 +138,20 @@ async fn run(spawner: Spawner) { static SERVICE_CONTEXT: StaticCell = StaticCell::new(); let service_context = SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([device0, device1]); + let registration = ArrayRegistration { + psus: [device0, device1], + service_senders: [NoopSender], + }; - static SERVICE: StaticCell>> = - StaticCell::new(); + static SERVICE: StaticCell = StaticCell::new(); let service = SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration.as_slice(), + registration, service_context, power_policy_service::service::config::Config::default(), ))); spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [device0, device1], [ device0_event_channel.dyn_receiver(), @@ -275,13 +283,13 @@ async fn run(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers< + psu_events: ArrayEventReceivers< 'static, 2, DeviceType, channel::DynamicReceiver<'static, power_policy_interface::psu::event::EventData>, >, - power_policy: &'static Mutex>, + power_policy: &'static ServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/service.rs b/examples/std/src/bin/type_c/service.rs index a03d7c29..c11d91a7 100644 --- a/examples/std/src/bin/type_c/service.rs +++ b/examples/std/src/bin/type_c/service.rs @@ -5,13 +5,15 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ado::Ado; use embedded_usb_pd::type_c::Current; use log::*; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use std_examples::type_c::mock_controller::Wrapper; @@ -30,6 +32,11 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 1, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn controller_task( wrapper: &'static Wrapper<'static>, @@ -72,13 +79,14 @@ async fn task(spawner: Spawner) { let (wrapper, policy_receiver, controller, state) = create_wrapper(controller_context); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 1]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper.ports[0].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper.ports[0].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -111,7 +119,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), + ArrayEventReceivers::new([&wrapper.ports[0].proxy], [policy_receiver]), power_service, )); spawner.must_spawn(type_c_service_task(type_c_service, [wrapper], cfu_client)); @@ -143,8 +151,8 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 1, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/examples/std/src/bin/type_c/ucsi.rs b/examples/std/src/bin/type_c/ucsi.rs index 24d0cb9c..ba6d9cd0 100644 --- a/examples/std/src/bin/type_c/ucsi.rs +++ b/examples/std/src/bin/type_c/ucsi.rs @@ -7,6 +7,7 @@ use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embedded_services::GlobalRawMutex; use embedded_services::IntrusiveList; +use embedded_services::event::NoopSender; use embedded_usb_pd::GlobalPortId; use embedded_usb_pd::ucsi::lpm::get_connector_capability::OperationModeFlags; use embedded_usb_pd::ucsi::ppm::ack_cc_ci::Ack; @@ -16,7 +17,8 @@ use embedded_usb_pd::ucsi::{Command, lpm, ppm}; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -37,6 +39,11 @@ const CFU1_ID: u8 = 0x01; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 2, NoopSender, 1>>, +>; + #[embassy_executor::task] async fn opm_task(context: &'static Context, state: [&'static mock_controller::ControllerState; NUM_PD_CONTROLLERS]) { const CAPABILITY: PowerCapability = PowerCapability { @@ -179,8 +186,8 @@ async fn wrapper_task(wrapper: &'static mock_controller::Wrapper<'static>) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 2, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } @@ -302,13 +309,14 @@ async fn task(spawner: Spawner) { static POWER_SERVICE_CONTEXT: StaticCell = StaticCell::new(); let power_service_context = POWER_SERVICE_CONTEXT.init(power_policy_service::service::context::Context::new()); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 2]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy]); + let power_policy_registration = ArrayRegistration { + psus: [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -361,7 +369,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [&wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy], [policy_receiver0, policy_receiver1], ), diff --git a/examples/std/src/bin/type_c/unconstrained.rs b/examples/std/src/bin/type_c/unconstrained.rs index 813e8052..a11b38ee 100644 --- a/examples/std/src/bin/type_c/unconstrained.rs +++ b/examples/std/src/bin/type_c/unconstrained.rs @@ -8,12 +8,14 @@ use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use embassy_sync::pubsub::PubSubChannel; use embassy_time::Timer; +use embedded_services::event::NoopSender; use embedded_services::{GlobalRawMutex, IntrusiveList}; use embedded_usb_pd::GlobalPortId; use log::*; use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu; -use power_policy_service::psu::EventReceivers; +use power_policy_service::psu::ArrayEventReceivers; +use power_policy_service::service::registration::ArrayRegistration; use static_cell::StaticCell; use std_examples::type_c::mock_controller; use type_c_service::service::Service; @@ -39,6 +41,11 @@ const DELAY_MS: u64 = 1000; type DeviceType = Mutex>; +type PowerPolicyServiceType = Mutex< + GlobalRawMutex, + power_policy_service::service::Service<'static, ArrayRegistration<'static, DeviceType, 3, NoopSender, 1>>, +>; + #[embassy_executor::task(pool_size = 3)] async fn controller_task(wrapper: &'static mock_controller::Wrapper<'static>) { loop { @@ -172,17 +179,18 @@ async fn task(spawner: Spawner) { crate::mock_controller::Validator, )); - static POWER_POLICY_PSU_REGISTRATION: StaticCell<[&DeviceType; 3]> = StaticCell::new(); - let psu_registration = POWER_POLICY_PSU_REGISTRATION.init([ - &wrapper0.ports[0].proxy, - &wrapper1.ports[0].proxy, - &wrapper2.ports[0].proxy, - ]); + let power_policy_registration = ArrayRegistration { + psus: [ + &wrapper0.ports[0].proxy, + &wrapper1.ports[0].proxy, + &wrapper2.ports[0].proxy, + ], + service_senders: [NoopSender], + }; - static POWER_SERVICE: StaticCell>> = - StaticCell::new(); + static POWER_SERVICE: StaticCell = StaticCell::new(); let power_service = POWER_SERVICE.init(Mutex::new(power_policy_service::service::Service::new( - psu_registration, + power_policy_registration, power_service_context, power_policy_service::service::config::Config::default(), ))); @@ -212,7 +220,7 @@ async fn task(spawner: Spawner) { let cfu_client = CfuClient::new(&CFU_CLIENT).await; spawner.must_spawn(power_policy_task( - EventReceivers::new( + ArrayEventReceivers::new( [ &wrapper0.ports[0].proxy, &wrapper1.ports[0].proxy, @@ -283,8 +291,8 @@ async fn task(spawner: Spawner) { #[embassy_executor::task] async fn power_policy_task( - psu_events: EventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, - power_policy: &'static Mutex>, + psu_events: ArrayEventReceivers<'static, 3, DeviceType, DynamicReceiver<'static, psu::event::EventData>>, + power_policy: &'static PowerPolicyServiceType, ) { power_policy_service::service::task::task(psu_events, power_policy).await; } diff --git a/power-policy-interface/src/psu/mod.rs b/power-policy-interface/src/psu/mod.rs index 323b8318..bae5ced5 100644 --- a/power-policy-interface/src/psu/mod.rs +++ b/power-policy-interface/src/psu/mod.rs @@ -1,4 +1,6 @@ //! Device struct and methods +use embedded_services::named::Named; + use crate::capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}; pub mod event; @@ -280,7 +282,7 @@ pub struct Response { } /// Trait for PSU devices -pub trait Psu { +pub trait Psu: Named { /// Disconnect power from this device fn disconnect(&mut self) -> impl Future>; /// Connect this device to provide power to an external connection @@ -291,6 +293,4 @@ pub trait Psu { fn state(&self) -> &State; /// Return a mutable reference to the current PSU state fn state_mut(&mut self) -> &mut State; - /// Return the name of the PSU - fn name(&self) -> &'static str; } diff --git a/power-policy-interface/src/service/event.rs b/power-policy-interface/src/service/event.rs index 9888142e..40874119 100644 --- a/power-policy-interface/src/service/event.rs +++ b/power-policy-interface/src/service/event.rs @@ -6,38 +6,73 @@ use crate::{ service::UnconstrainedState, }; +/// Event data broadcast from the service. +/// +/// This enum doesn't contain a reference to the device and is suitable +/// for receivers that don't need to know which device triggered the event +/// and allows for receivers that don't need to be generic over the device type. +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EventData { + /// Consumer disconnected + ConsumerDisconnected, + /// Consumer connected + ConsumerConnected(ConsumerPowerCapability), + /// Provider disconnected + ProviderDisconnected, + /// Provider connected + ProviderConnected(ProviderPowerCapability), + /// Unconstrained state changed + Unconstrained(UnconstrainedState), +} + +impl<'device, PSU: Lockable> From> for EventData +where + PSU::Inner: Psu, +{ + fn from(value: Event<'device, PSU>) -> Self { + match value { + Event::ConsumerDisconnected(_) => EventData::ConsumerDisconnected, + Event::ConsumerConnected(_, capability) => EventData::ConsumerConnected(capability), + Event::ProviderDisconnected(_) => EventData::ProviderDisconnected, + Event::ProviderConnected(_, capability) => EventData::ProviderConnected(capability), + Event::Unconstrained(unconstrained) => EventData::Unconstrained(unconstrained), + } + } +} + /// Events broadcast from the service. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Event<'device, D: Lockable> +pub enum Event<'device, PSU: Lockable> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Consumer disconnected - ConsumerDisconnected(&'device D), + ConsumerDisconnected(&'device PSU), /// Consumer connected - ConsumerConnected(&'device D, ConsumerPowerCapability), + ConsumerConnected(&'device PSU, ConsumerPowerCapability), /// Provider disconnected - ProviderDisconnected(&'device D), + ProviderDisconnected(&'device PSU), /// Provider connected - ProviderConnected(&'device D, ProviderPowerCapability), + ProviderConnected(&'device PSU, ProviderPowerCapability), /// Unconstrained state changed Unconstrained(UnconstrainedState), } -impl<'device, D> Clone for Event<'device, D> +impl<'device, PSU> Clone for Event<'device, PSU> where - D: Lockable, - D::Inner: Psu, + PSU: Lockable, + PSU::Inner: Psu, { fn clone(&self) -> Self { *self } } -impl<'device, D> Copy for Event<'device, D> +impl<'device, PSU> Copy for Event<'device, PSU> where - D: Lockable, - D::Inner: Psu, + PSU: Lockable, + PSU::Inner: Psu, { } diff --git a/power-policy-service/Cargo.toml b/power-policy-service/Cargo.toml index 2f0552f0..13d23611 100644 --- a/power-policy-service/Cargo.toml +++ b/power-policy-service/Cargo.toml @@ -32,7 +32,7 @@ env_logger = "0.11.8" log = { workspace = true } # TODO: figure out why enabling the log feature here causes running tests at the workspace level to fail to compile # Uncomment this line to enable log output in tests -#power-policy-service = { workspace = true, features = ["log"] } +# power-policy-service = { workspace = true, features = ["log"] } [features] default = [] diff --git a/power-policy-service/src/psu.rs b/power-policy-service/src/psu.rs index 47ce355d..81bd2133 100644 --- a/power-policy-service/src/psu.rs +++ b/power-policy-service/src/psu.rs @@ -7,7 +7,7 @@ use power_policy_interface::psu::Psu; use power_policy_interface::psu::event::{Event, EventData}; /// Struct used to contain PSU event receivers and manage mapping from a receiver to its corresponding device. -pub struct EventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> +pub struct ArrayEventReceivers<'a, const N: usize, PSU: Lockable, R: Receiver> where PSU::Inner: Psu, { @@ -15,7 +15,7 @@ where pub receivers: [R; N], } -impl<'a, const N: usize, PSU: Lockable, R: Receiver> EventReceivers<'a, N, PSU, R> +impl<'a, const N: usize, PSU: Lockable, R: Receiver> ArrayEventReceivers<'a, N, PSU, R> where PSU::Inner: Psu, { diff --git a/power-policy-service/src/service/consumer.rs b/power-policy-service/src/service/consumer.rs index ea271d62..b52b0e2b 100644 --- a/power-policy-service/src/service/consumer.rs +++ b/power-policy-service/src/service/consumer.rs @@ -1,36 +1,32 @@ use core::cmp::Ordering; +use embedded_services::named::Named; use embedded_services::{debug, error}; use super::*; use power_policy_interface::capability::ConsumerFlags; use power_policy_interface::charger::Device as ChargerDevice; +use power_policy_interface::psu; use power_policy_interface::service::event::Event as ServiceEvent; use power_policy_interface::{capability::ConsumerPowerCapability, charger::PolicyEvent, psu::PsuState}; /// State of the current consumer #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct AvailableConsumer<'device, D: Lockable> -where - D::Inner: Psu, -{ +pub struct AvailableConsumer<'device, Psu: Lockable> { /// Device reference - pub psu: &'device D, + pub psu: &'device Psu, /// The power capability of the currently connected consumer pub consumer_power_capability: ConsumerPowerCapability, } -impl<'device, D: Lockable> Clone for AvailableConsumer<'device, D> -where - D::Inner: Psu, -{ +impl<'device, Psu: Lockable> Clone for AvailableConsumer<'device, Psu> { fn clone(&self) -> Self { *self } } -impl<'device, D: Lockable> Copy for AvailableConsumer<'device, D> where D::Inner: Psu {} +impl<'device, Psu: Lockable> Copy for AvailableConsumer<'device, Psu> {} /// Compare two consumer capabilities to determine which one is better /// @@ -46,16 +42,13 @@ fn cmp_consumer_capability( (a.capability, a_is_current).cmp(&(b.capability, b_is_current)) } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Iterate over all devices to determine what is best power port provides the highest power - async fn find_best_consumer(&self) -> Result>, Error> { + async fn find_best_consumer(&self) -> Result>, Error> { let mut best_consumer = None; let current_consumer = self.state.current_consumer_state.as_ref().map(|f| f.psu); - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let locked_psu = psu.lock().await; let consumer_capability = locked_psu.state().consumer_capability; // Don't consider consumers below minimum threshold @@ -108,7 +101,7 @@ where async fn update_unconstrained_state(&mut self) -> Result<(), Error> { // Count how many available unconstrained devices we have let mut unconstrained_new = UnconstrainedState::default(); - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { if let Some(capability) = psu.lock().await.state().consumer_capability { if capability.flags.unconstrained_power() { unconstrained_new.available += 1; @@ -133,7 +126,10 @@ where } /// Common logic to execute after a consumer is connected - async fn post_consumer_connected(&mut self, connected_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { + async fn post_consumer_connected( + &mut self, + connected_consumer: AvailableConsumer<'device, Reg::Psu>, + ) -> Result<(), Error> { self.state.current_consumer_state = Some(connected_consumer); // todo: review the delay time embassy_time::Timer::after_millis(800).await; @@ -191,7 +187,7 @@ where } /// Connect to a new consumer - async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'a, PSU>) -> Result<(), Error> { + async fn connect_new_consumer(&mut self, new_consumer: AvailableConsumer<'device, Reg::Psu>) -> Result<(), Error> { // Handle our current consumer if let Some(current_consumer) = self.state.current_consumer_state { if ptr::eq(current_consumer.psu, new_consumer.psu) diff --git a/power-policy-service/src/service/mod.rs b/power-policy-service/src/service/mod.rs index 7ea2feee..3934bc34 100644 --- a/power-policy-service/src/service/mod.rs +++ b/power-policy-service/src/service/mod.rs @@ -5,9 +5,11 @@ pub mod config; pub mod consumer; pub mod context; pub mod provider; +pub mod registration; pub mod task; -use embedded_services::{error, info, sync::Lockable}; +use embedded_services::named::Named; +use embedded_services::{error, event::Sender, info, sync::Lockable}; use power_policy_interface::{ capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, @@ -18,15 +20,17 @@ use power_policy_interface::{ service::{UnconstrainedState, event::Event as ServiceEvent}, }; +use crate::service::registration::Registration; + const MAX_CONNECTED_PROVIDERS: usize = 4; #[derive(Clone)] -struct InternalState<'device, D: Lockable> +struct InternalState<'device, PSU: Lockable> where - D::Inner: Psu, + PSU::Inner: Psu, { /// Current consumer state, if any - current_consumer_state: Option>, + current_consumer_state: Option>, /// Current provider global state current_provider_state: provider::State, /// System unconstrained power @@ -35,9 +39,9 @@ where connected_providers: heapless::FnvIndexSet, } -impl Default for InternalState<'_, D> +impl Default for InternalState<'_, PSU> where - D::Inner: Psu, + PSU::Inner: Psu, { fn default() -> Self { Self { @@ -50,29 +54,23 @@ where } /// Power policy service -pub struct Service<'a, PSU: Lockable> -where - PSU::Inner: Psu, -{ +pub struct Service<'device, Reg: Registration<'device>> { + /// Service registration + registration: Reg, /// Power policy context - pub context: &'a context::Context, - /// PSU devices - psu_devices: &'a [&'a PSU], + pub context: &'device context::Context, /// State - state: InternalState<'a, PSU>, + state: InternalState<'device, Reg::Psu>, /// Config config: config::Config, } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Create a new power policy - pub fn new(psu_devices: &'a [&'a PSU], context: &'a context::Context, config: config::Config) -> Self { + pub fn new(registration: Reg, context: &'device context::Context, config: config::Config) -> Self { Self { + registration, context, - psu_devices, state: InternalState::default(), config, } @@ -82,7 +80,7 @@ where pub async fn compute_total_provider_power_mw(&self) -> u32 { let mut total = 0; - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let psu = psu.lock().await; total += psu .state() @@ -94,7 +92,7 @@ where total } - async fn process_notify_attach(&self, device: &PSU) { + async fn process_notify_attach(&self, device: &'device Reg::Psu) { let mut device = device.lock().await; info!("({}): Received notify attached", device.name()); if let Err(e) = device.state_mut().attach() { @@ -102,18 +100,21 @@ where } } - async fn process_notify_detach(&mut self, device: &PSU) -> Result<(), Error> { + async fn process_notify_detach(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { { let mut device = device.lock().await; info!("({}): Received notify detached", device.name()); device.state_mut().detach(); } - self.update_current_consumer().await + + self.post_provider_removed(device).await; + self.update_current_consumer().await?; + Ok(()) } async fn process_notify_consumer_power_capability( &mut self, - device: &PSU, + device: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { { @@ -137,7 +138,7 @@ where async fn process_request_provider_power_capabilities( &mut self, - requester: &'a PSU, + requester: &'device Reg::Psu, capability: Option, ) -> Result<(), Error> { { @@ -152,7 +153,7 @@ where .update_requested_provider_power_capability(capability) { error!( - "({}): Invalid state for notify consumer capability, catching up: {:#?}", + "({}): Invalid state for notify provider capability, catching up: {:#?}", requester.name(), e, ); @@ -162,54 +163,33 @@ where self.connect_provider(requester).await } - async fn process_notify_disconnect(&mut self, device: &'a PSU) -> Result<(), Error> { - let mut locked_device = device.lock().await; - info!("({}): Received notify disconnect", locked_device.name()); - - if let Err(e) = locked_device.state_mut().disconnect(true) { - error!( - "({}): Invalid state for notify disconnect, catching up: {:#?}", - locked_device.name(), - e, - ); - } - - if self - .state - .current_consumer_state - .as_ref() - .is_some_and(|current| ptr::eq(current.psu, device)) + async fn process_notify_disconnect(&mut self, device: &'device Reg::Psu) -> Result<(), Error> { { - info!("({}): Connected consumer disconnected", locked_device.name()); - self.disconnect_chargers().await?; + let mut locked_device = device.lock().await; + info!("({}): Received notify disconnect", locked_device.name()); - self.broadcast_event(ServiceEvent::ConsumerDisconnected(device)).await; + if let Err(e) = locked_device.state_mut().disconnect(true) { + error!( + "({}): Invalid state for notify disconnect, catching up: {:#?}", + locked_device.name(), + e, + ); + } } - self.remove_connected_provider(device).await; + self.post_provider_removed(device).await; self.update_current_consumer().await?; Ok(()) } /// Send an event to all registered listeners - async fn broadcast_event(&mut self, _message: ServiceEvent<'a, PSU>) { - // TODO: Add this back as part of the migration away from comms - // See https://github.com/OpenDevicePartnership/embedded-services/issues/742 - } - - /// Common logic for when a provider is disconnected - /// - /// Returns true if the device was operating as a provider - async fn remove_connected_provider(&mut self, psu: &'a PSU) -> bool { - if self.state.connected_providers.remove(&(psu as *const PSU as usize)) { - self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; - true - } else { - false + async fn broadcast_event(&mut self, event: ServiceEvent<'device, Reg::Psu>) { + for sender in self.registration.event_senders() { + sender.send(event).await; } } - pub async fn process_psu_event(&mut self, event: PsuEvent<'a, PSU>) -> Result<(), Error> { + pub async fn process_psu_event(&mut self, event: PsuEvent<'device, Reg::Psu>) -> Result<(), Error> { let device = event.psu; match event.event { PsuEventData::Attached => { diff --git a/power-policy-service/src/service/provider.rs b/power-policy-service/src/service/provider.rs index dd174d15..02029e61 100644 --- a/power-policy-service/src/service/provider.rs +++ b/power-policy-service/src/service/provider.rs @@ -6,6 +6,7 @@ use core::ptr; use embedded_services::debug; +use embedded_services::named::Named; use super::*; @@ -27,12 +28,9 @@ pub(super) struct State { state: PowerState, } -impl<'a, PSU: Lockable> Service<'a, PSU> -where - PSU::Inner: Psu, -{ +impl<'device, Reg: Registration<'device>> Service<'device, Reg> { /// Attempt to connect the requester as a provider - pub(super) async fn connect_provider(&mut self, requester: &'a PSU) -> Result<(), Error> { + pub(super) async fn connect_provider(&mut self, requester: &'device Reg::Psu) -> Result<(), Error> { let requested_power_capability = { let requester = requester.lock().await; debug!("({}): Attempting to connect as provider", requester.name()); @@ -48,7 +46,7 @@ where // Determine total requested power draw let mut total_power_mw = 0; - for psu in self.psu_devices.iter() { + for psu in self.registration.psus() { let target_provider_cap = if ptr::eq(*psu, requester) { // Use the requester's requested power capability // this handles both new connections and upgrade requests @@ -104,9 +102,45 @@ where } /// Common logic for after a provider has successfully connected - async fn post_provider_connected(&mut self, requester: &'a PSU, target_power: ProviderPowerCapability) { - let _ = self.state.connected_providers.insert(requester as *const PSU as usize); + async fn post_provider_connected(&mut self, requester: &'device Reg::Psu, target_power: ProviderPowerCapability) { + if self + .state + .connected_providers + .insert(requester as *const Reg::Psu as usize) + .is_err() + { + error!("Tracked providers set is full"); + } self.broadcast_event(ServiceEvent::ProviderConnected(requester, target_power)) .await; } + + /// Common logic for when a provider is removed + /// + /// Returns true if the device was operating as a provider + pub(super) async fn post_provider_removed(&mut self, psu: &'device Reg::Psu) -> bool { + if self + .state + .connected_providers + .remove(&(psu as *const Reg::Psu as usize)) + { + // Determine total requested power draw + let mut total_power_mw = 0; + for psu in self.registration.psus() { + let target_provider_cap = psu.lock().await.state().connected_provider_capability(); + total_power_mw += target_provider_cap.map_or(0, |cap| cap.capability.max_power_mw()); + } + + if total_power_mw > self.config.limited_power_threshold_mw { + self.state.current_provider_state.state = PowerState::Limited; + } else { + self.state.current_provider_state.state = PowerState::Unlimited; + } + + self.broadcast_event(ServiceEvent::ProviderDisconnected(psu)).await; + true + } else { + false + } + } } diff --git a/power-policy-service/src/service/registration.rs b/power-policy-service/src/service/registration.rs new file mode 100644 index 00000000..e7de916e --- /dev/null +++ b/power-policy-service/src/service/registration.rs @@ -0,0 +1,48 @@ +//! Code related to registration with the power policy service. +use embedded_services::{event::Sender, sync::Lockable}; +use power_policy_interface::{psu, service::event::Event as ServiceEvent}; + +/// Registration trait that abstracts over various registration details. +pub trait Registration<'device> { + type Psu: Lockable + 'device; + type ServiceSender: Sender>; + + /// Returns a slice to access PSU devices + fn psus(&self) -> &[&'device Self::Psu]; + /// Returns a slice to access power policy event senders + fn event_senders(&mut self) -> &mut [Self::ServiceSender]; +} + +/// A registration implementation based around arrays +pub struct ArrayRegistration< + 'device, + Psu: Lockable + 'device, + const PSU_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> { + /// Array of registered PSUs + pub psus: [&'device Psu; PSU_COUNT], + /// Array of power policy service event senders + pub service_senders: [ServiceSender; SERVICE_SENDER_COUNT], +} + +impl< + 'device, + Psu: Lockable + 'device, + const PSU_COUNT: usize, + ServiceSender: Sender>, + const SERVICE_SENDER_COUNT: usize, +> Registration<'device> for ArrayRegistration<'device, Psu, PSU_COUNT, ServiceSender, SERVICE_SENDER_COUNT> +{ + type Psu = Psu; + type ServiceSender = ServiceSender; + + fn psus(&self) -> &[&'device Self::Psu] { + &self.psus + } + + fn event_senders(&mut self) -> &mut [Self::ServiceSender] { + &mut self.service_senders + } +} diff --git a/power-policy-service/src/service/task.rs b/power-policy-service/src/service/task.rs index d394957d..41c3e246 100644 --- a/power-policy-service/src/service/task.rs +++ b/power-policy-service/src/service/task.rs @@ -1,25 +1,23 @@ use embedded_services::{error, info, sync::Lockable}; use embedded_services::event::Receiver; -use power_policy_interface::psu::Psu; use power_policy_interface::psu::event::EventData; +use crate::service::registration::Registration; + use super::Service; /// Runs the power policy task. pub async fn task< - 'a, + 'device, const PSU_COUNT: usize, - S: Lockable>, - PSU: Lockable, - R: Receiver, + S: Lockable>, + Reg: Registration<'device>, + PsuReceiver: Receiver, >( - mut psu_events: crate::psu::EventReceivers<'a, PSU_COUNT, PSU, R>, - policy: &'a S, -) -> ! -where - PSU::Inner: Psu, -{ + mut psu_events: crate::psu::ArrayEventReceivers<'device, PSU_COUNT, Reg::Psu, PsuReceiver>, + policy: &'device S, +) -> ! { info!("Starting power policy task"); loop { let event = psu_events.wait_event().await; diff --git a/power-policy-service/tests/common/mock.rs b/power-policy-service/tests/common/mock.rs index f117b73b..ab359787 100644 --- a/power-policy-service/tests/common/mock.rs +++ b/power-policy-service/tests/common/mock.rs @@ -1,8 +1,9 @@ #![allow(clippy::unwrap_used)] +#![allow(dead_code)] use embassy_sync::signal::Signal; -use embedded_services::{GlobalRawMutex, event::Sender, info}; +use embedded_services::{GlobalRawMutex, event::Sender, info, named::Named}; use power_policy_interface::{ - capability::{ConsumerFlags, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + capability::{ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability}, psu::{Error, Psu, State, event::EventData}, }; @@ -42,18 +43,40 @@ impl<'a, S: Sender> Mock<'a, S> { self.fn_call.signal((num_fn_calls + 1, fn_call)); } - pub async fn simulate_consumer_connection(&mut self, capability: PowerCapability) { + pub async fn simulate_consumer_connection(&mut self, capability: ConsumerPowerCapability) { + self.sender.send(EventData::Attached).await; + self.sender + .send(EventData::UpdatedConsumerCapability(Some(capability))) + .await; + } + + pub async fn simulate_detach(&mut self) { + self.sender.send(EventData::Detached).await; + } + + pub async fn simulate_provider_connection(&mut self, capability: PowerCapability) { self.sender.send(EventData::Attached).await; - let capability = Some(ConsumerPowerCapability { + let capability = Some(ProviderPowerCapability { capability, - flags: ConsumerFlags::none(), + flags: ProviderFlags::none(), }); - self.sender.send(EventData::UpdatedConsumerCapability(capability)).await; + self.sender + .send(EventData::RequestedProviderCapability(capability)) + .await; } - pub async fn simulate_detach(&mut self) { - self.sender.send(EventData::Detached).await; + pub async fn simulate_disconnect(&mut self) { + self.sender.send(EventData::Disconnected).await; + } + + pub async fn simulate_update_requested_provider_power_capability( + &mut self, + capability: Option, + ) { + self.sender + .send(power_policy_interface::psu::event::EventData::RequestedProviderCapability(capability)) + .await } } @@ -83,7 +106,9 @@ impl<'a, S: Sender> Psu for Mock<'a, S> { fn state_mut(&mut self) -> &mut State { &mut self.state } +} +impl<'a, S: Sender> Named for Mock<'a, S> { fn name(&self) -> &'static str { self.name } diff --git a/power-policy-service/tests/common/mod.rs b/power-policy-service/tests/common/mod.rs index 5ec32058..69261909 100644 --- a/power-policy-service/tests/common/mod.rs +++ b/power-policy-service/tests/common/mod.rs @@ -1,4 +1,8 @@ #![allow(clippy::unwrap_used)] +#![allow(dead_code)] +#![allow(clippy::panic)] +use std::mem::ManuallyDrop; + use embassy_futures::{ join::join, select::{Either, select}, @@ -11,10 +15,13 @@ use embassy_sync::{ }; use embassy_time::{Duration, with_timeout}; use embedded_services::GlobalRawMutex; -use power_policy_interface::capability::PowerCapability; use power_policy_interface::psu::event::EventData; -use power_policy_service::psu::EventReceivers; +use power_policy_interface::{ + capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability}, + service::{UnconstrainedState, event::Event as ServiceEvent}, +}; use power_policy_service::service::Service; +use power_policy_service::{psu::ArrayEventReceivers, service::registration::ArrayRegistration}; pub mod mock; @@ -38,12 +45,21 @@ pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); const EVENT_CHANNEL_SIZE: usize = 4; pub type DeviceType<'a> = Mutex>>; -pub type ServiceType<'a> = Service<'a, DeviceType<'a>>; - -async fn power_policy_task<'a, const N: usize>( - completion_signal: &'a Signal, - mut power_policy: ServiceType<'a>, - mut event_receivers: EventReceivers<'a, N, DeviceType<'a>, DynamicReceiver<'a, EventData>>, +pub type ServiceType<'device, 'sender> = Service< + 'device, + ArrayRegistration< + 'device, + DeviceType<'device>, + 2, + DynamicSender<'sender, ServiceEvent<'device, DeviceType<'device>>>, + 1, + >, +>; + +async fn power_policy_task<'device, 'sender, const N: usize>( + completion_signal: &'device Signal, + mut power_policy: ServiceType<'device, 'sender>, + mut event_receivers: ArrayEventReceivers<'device, N, DeviceType<'device>, DynamicReceiver<'device, EventData>>, ) { while let Either::First(result) = select(event_receivers.wait_event(), completion_signal.wait()).await { power_policy.process_psu_event(result).await.unwrap(); @@ -63,15 +79,16 @@ async fn power_policy_task<'a, const N: usize>( /// ``` /// However, `impl (Future + 'a)` is not real syntax. This could be done with the unstable feature type_alias_impl_trait, /// but we use this helper trait so as to not require use of nightly. -pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a>: - FnOnce(Arg0, Arg1, Arg2, Arg3) -> Self::Fut +pub trait TestArgsFnOnce<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a>: + FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Self::Fut { type Fut: Future; } -impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3> for F +impl<'a, Arg0: 'a, Arg1: 'a, Arg2: 'a, Arg3: 'a, Arg4: 'a, F, Fut> TestArgsFnOnce<'a, Arg0, Arg1, Arg2, Arg3, Arg4> + for F where - F: FnOnce(Arg0, Arg1, Arg2, Arg3) -> Fut, + F: FnOnce(Arg0, Arg1, Arg2, Arg3, Arg4) -> Fut, Fut: Future, { type Fut = Fut; @@ -81,9 +98,10 @@ pub async fn run_test(timeout: Duration, test: F) where for<'a> F: TestArgsFnOnce< 'a, - &'a Mutex>>, + DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + &'a DeviceType<'a>, &'a Signal, - &'a Mutex>>, + &'a DeviceType<'a>, &'a Signal, >, { @@ -109,11 +127,25 @@ where let device1 = Mutex::new(Mock::new("PSU1", device1_sender, &device1_signal)); let service_context = power_policy_service::service::context::Context::new(); - let psu_registration = [&device0, &device1]; let completion_signal = Signal::new(); + // Ideally F would have two lifetime arguments: 'device and 'sender because the event type requires 'device: 'sender. + // But Rust doesn't currently support syntax like `for<'device, 'sender> ... where 'device: 'sender`. So we just + // use a single lifetime. However, the unified lifetime makes the drop-checker think that dropping the channel + // could be unsafe. We use ManuallyDrop to disable the drop and make the drop-checker happy. None of the types + // here do any clean-up in their Drop impls so we don't have to worry about any sort of leaks. + let service_event_channel: ManuallyDrop< + Channel>, EVENT_CHANNEL_SIZE>, + > = ManuallyDrop::new(Channel::new()); + let service_receiver = service_event_channel.dyn_receiver(); + + let power_policy_registration = ArrayRegistration { + psus: [&device0, &device1], + service_senders: [service_event_channel.dyn_sender()], + }; + let power_policy = - power_policy_service::service::Service::new(psu_registration.as_slice(), &service_context, Default::default()); + power_policy_service::service::Service::new(power_policy_registration, &service_context, Default::default()); with_timeout( timeout, @@ -121,10 +153,10 @@ where power_policy_task( &completion_signal, power_policy, - EventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), + ArrayEventReceivers::new([&device0, &device1], [device0_receiver, device1_receiver]), ), async { - test(&device0, &device0_signal, &device1, &device1_signal).await; + test(service_receiver, &device0, &device0_signal, &device1, &device1_signal).await; completion_signal.signal(()); }, ), @@ -132,3 +164,61 @@ where .await .unwrap(); } + +pub async fn assert_consumer_disconnected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, +) { + let ServiceEvent::ConsumerDisconnected(device) = receiver.receive().await else { + panic!("Expected ConsumerDisconnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); +} + +pub async fn assert_consumer_connected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, + expected_capability: ConsumerPowerCapability, +) { + let ServiceEvent::ConsumerConnected(device, capability) = receiver.receive().await else { + panic!("Expected ConsumerConnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); + assert_eq!(capability, expected_capability); +} + +pub async fn assert_provider_disconnected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, +) { + let ServiceEvent::ProviderDisconnected(device) = receiver.receive().await else { + panic!("Expected ProviderDisconnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); +} + +pub async fn assert_provider_connected<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_device: &DeviceType<'a>, + expected_capability: ProviderPowerCapability, +) { + let ServiceEvent::ProviderConnected(device, capability) = receiver.receive().await else { + panic!("Expected ProviderConnected event"); + }; + assert_eq!(device as *const _, expected_device as *const _); + assert_eq!(capability, expected_capability); +} + +pub async fn assert_unconstrained<'a>( + receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + expected_state: UnconstrainedState, +) { + let ServiceEvent::Unconstrained(state) = receiver.receive().await else { + panic!("Expected Unconstrained event"); + }; + assert_eq!(state, expected_state); +} + +pub fn assert_no_event<'a>(receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>) { + assert!(receiver.try_receive().is_err()); +} diff --git a/power-policy-service/tests/consumer.rs b/power-policy-service/tests/consumer.rs index 94feac60..9fd5ac29 100644 --- a/power-policy-service/tests/consumer.rs +++ b/power-policy-service/tests/consumer.rs @@ -1,5 +1,6 @@ #![allow(clippy::unwrap_used)] -use embassy_sync::{channel::DynamicSender, mutex::Mutex, signal::Signal}; +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; use embassy_time::{Duration, TimeoutError, with_timeout}; use embedded_services::GlobalRawMutex; use embedded_services::info; @@ -8,27 +9,32 @@ use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability} mod common; use common::LOW_POWER; -use power_policy_interface::psu::event::EventData; +use power_policy_interface::service::event::Event as ServiceEvent; +use crate::common::DeviceType; +use crate::common::assert_no_event; use crate::common::{ - DEFAULT_TIMEOUT, HIGH_POWER, - mock::{FnCall, Mock}, - run_test, + DEFAULT_TIMEOUT, HIGH_POWER, assert_consumer_connected, assert_consumer_disconnected, mock::FnCall, run_test, }; const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); /// Test the basic consumer flow with a single device. -async fn test_single( - device0: &Mutex>>, +async fn test_single<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, device0_signal: &Signal, - _device1: &Mutex>>, + _device1: &DeviceType<'a>, _device1_signal: &Signal, ) { info!("Running test_single"); // Test initial connection { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -41,6 +47,16 @@ async fn test_single( ) ); device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } // Test detach { @@ -52,20 +68,29 @@ async fn test_single( Err(TimeoutError) ); device0_signal.reset(); + + assert_consumer_disconnected(service_receiver, device0).await; } + + assert_no_event(service_receiver); } /// Test swapping to a higher powered device. -async fn test_swap_higher( - device0: &Mutex>>, +async fn test_swap_higher<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, device0_signal: &Signal, - device1: &Mutex>>, + device1: &DeviceType<'a>, device1_signal: &Signal, ) { info!("Running test_swap_higher"); // Device0 connection at low power { - device0.lock().await.simulate_consumer_connection(LOW_POWER).await; + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -78,10 +103,24 @@ async fn test_swap_higher( ) ); device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } // Device1 connection at high power { - device1.lock().await.simulate_consumer_connection(HIGH_POWER).await; + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; assert_eq!( with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), @@ -100,6 +139,19 @@ async fn test_swap_higher( ) ); device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } // Test detach device1, should reconnect device0 { @@ -122,16 +174,153 @@ async fn test_swap_higher( ) ); device0_signal.reset(); + + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; } + + assert_no_event(service_receiver); +} + +/// Test a disconnect initiated by the current consumer. +async fn test_disconnect<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_disconnect"); + // Device0 connection at low power + { + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + // Device1 connection at high power + { + device1 + .lock() + .await + .simulate_consumer_connection(HIGH_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + + // Test disconnect device1, should reconnect device0 + { + device1.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Consume the disconnect event generated by `simulate_disconnect` + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + } + + assert_no_event(service_receiver); } #[tokio::test] -async fn run_all_tests() { +async fn run_test_swap_higher() { run_test(DEFAULT_TIMEOUT, test_swap_higher).await; } -/// Run all tests, this is temporary to deal with 'static lifetimes until the intrusive list refactor is done. #[tokio::test] async fn run_test_single() { run_test(DEFAULT_TIMEOUT, test_single).await; } + +#[tokio::test] +async fn run_test_disconnect() { + run_test(DEFAULT_TIMEOUT, test_disconnect).await; +} diff --git a/power-policy-service/tests/provider.rs b/power-policy-service/tests/provider.rs new file mode 100644 index 00000000..c5a78911 --- /dev/null +++ b/power-policy-service/tests/provider.rs @@ -0,0 +1,282 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; +use embassy_time::{Duration, TimeoutError, with_timeout}; +use embedded_services::GlobalRawMutex; +use embedded_services::info; +use power_policy_interface::capability::ProviderFlags; +use power_policy_interface::capability::ProviderPowerCapability; + +mod common; + +use common::LOW_POWER; +use power_policy_interface::service::event::Event as ServiceEvent; + +use crate::common::DeviceType; +use crate::common::HIGH_POWER; +use crate::common::assert_no_event; +use crate::common::{DEFAULT_TIMEOUT, assert_provider_connected, assert_provider_disconnected, mock::FnCall, run_test}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +/// Test the basic provider flow with a single device. +async fn test_single<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, +) { + info!("Running test_single"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + // Test detach + { + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + assert_no_event(service_receiver); +} + +/// Test provider flow involving multiple devices and upgrading a provider's power capability. +async fn test_upgrade<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_upgrade"); + { + // Connect device0 at high power, default service config should allow this + device0.lock().await.simulate_provider_connection(HIGH_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + { + // Connect device1 at low power, default service config should allow this + device1.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + { + // Attempt to upgrade device1 to high power, power policy should reject this since device0 is already connected at high power + // Power policy will instead allow us to connect at low power + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + { + // Detach device0, this should allow us to upgrade device1 to high power + device0.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + { + // Attempt to upgrade device1 to high power should now succeed + device1 + .lock() + .await + .simulate_update_requested_provider_power_capability(Some(HIGH_POWER.into())) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device1_signal.reset(); + + assert_provider_connected( + service_receiver, + device1, + ProviderPowerCapability { + capability: HIGH_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + + assert_no_event(service_receiver); +} + +/// Test the provider disconnect flow +async fn test_disconnect<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + _device1: &DeviceType<'a>, + _device1_signal: &Signal, +) { + info!("Running test_disconnect"); + // Test initial connection + { + device0.lock().await.simulate_provider_connection(LOW_POWER).await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectProvider(ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_provider_connected( + service_receiver, + device0, + ProviderPowerCapability { + capability: LOW_POWER, + flags: ProviderFlags::none(), + }, + ) + .await; + } + // Test disconnect + { + device0.lock().await.simulate_disconnect().await; + + // Power policy shouldn't call any functions on disconnect so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await, + Err(TimeoutError) + ); + device0_signal.reset(); + + assert_provider_disconnected(service_receiver, device0).await; + } + + assert_no_event(service_receiver); +} + +#[tokio::test] +async fn run_test_single() { + run_test(DEFAULT_TIMEOUT, test_single).await; +} + +#[tokio::test] +async fn run_test_upgrade() { + run_test(DEFAULT_TIMEOUT, test_upgrade).await; +} + +#[tokio::test] +async fn run_test_disconnect() { + run_test(DEFAULT_TIMEOUT, test_disconnect).await; +} diff --git a/power-policy-service/tests/unconstrained.rs b/power-policy-service/tests/unconstrained.rs new file mode 100644 index 00000000..2bea4385 --- /dev/null +++ b/power-policy-service/tests/unconstrained.rs @@ -0,0 +1,171 @@ +#![allow(clippy::unwrap_used)] +use embassy_sync::channel::DynamicReceiver; +use embassy_sync::signal::Signal; +use embassy_time::TimeoutError; +use embassy_time::{Duration, with_timeout}; +use embedded_services::GlobalRawMutex; +use embedded_services::info; +use power_policy_interface::capability::{ConsumerFlags, ConsumerPowerCapability}; + +mod common; + +use common::LOW_POWER; +use power_policy_interface::service::UnconstrainedState; +use power_policy_interface::service::event::Event as ServiceEvent; + +use crate::common::DeviceType; +use crate::common::HIGH_POWER; +use crate::common::{ + DEFAULT_TIMEOUT, assert_consumer_connected, assert_consumer_disconnected, assert_no_event, assert_unconstrained, + mock::FnCall, run_test, +}; + +const PER_CALL_TIMEOUT: Duration = Duration::from_millis(1000); + +/// Test unconstrained consumer flow with multiple devices. +async fn test_unconstrained<'a>( + service_receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>, + device0: &DeviceType<'a>, + device0_signal: &Signal, + device1: &DeviceType<'a>, + device1_signal: &Signal, +) { + info!("Running test_unconstrained"); + { + // Connect device0, without unconstrained, + device0 + .lock() + .await + .simulate_consumer_connection(LOW_POWER.into()) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + + // Should not have any unconstrained events + assert!(service_receiver.try_receive().is_err()); + } + + { + // Connect device1 with unconstrained at HIGH_POWER to force power policy to select this consumer. + device1 + .lock() + .await + .simulate_consumer_connection(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }) + .await; + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + (1, FnCall::Disconnect) + ); + device0_signal.reset(); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }) + ) + ); + device1_signal.reset(); + + // Should receive a disconnect event from device0 first + assert_consumer_disconnected(service_receiver, device0).await; + + assert_consumer_connected( + service_receiver, + device1, + ConsumerPowerCapability { + capability: HIGH_POWER, + flags: ConsumerFlags::none().with_unconstrained_power(), + }, + ) + .await; + + assert_unconstrained( + service_receiver, + UnconstrainedState { + unconstrained: true, + available: 1, + }, + ) + .await; + } + + { + // Test detach device1, unconstrained state should change + device1.lock().await.simulate_detach().await; + + // Power policy shouldn't call any functions on detach so we'll timeout + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device1_signal.wait()).await, + Err(TimeoutError) + ); + + assert_eq!( + with_timeout(PER_CALL_TIMEOUT, device0_signal.wait()).await.unwrap(), + ( + 1, + FnCall::ConnectConsumer(ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }) + ) + ); + device0_signal.reset(); + + // Should receive a disconnect event from device1 first + assert_consumer_disconnected(service_receiver, device1).await; + + assert_consumer_connected( + service_receiver, + device0, + ConsumerPowerCapability { + capability: LOW_POWER, + flags: ConsumerFlags::none(), + }, + ) + .await; + + assert_unconstrained( + service_receiver, + UnconstrainedState { + unconstrained: false, + available: 0, + }, + ) + .await; + } + + assert_no_event(service_receiver); +} + +#[tokio::test] +async fn run_test_unconstrained() { + run_test(DEFAULT_TIMEOUT, test_unconstrained).await; +} diff --git a/type-c-service/src/wrapper/proxy.rs b/type-c-service/src/wrapper/proxy.rs index a14dc82a..170fd55d 100644 --- a/type-c-service/src/wrapper/proxy.rs +++ b/type-c-service/src/wrapper/proxy.rs @@ -1,5 +1,6 @@ use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embedded_services::named::Named; use power_policy_interface::psu::{CommandData as PolicyCommandData, InternalResponseData as PolicyResponseData, Psu}; pub struct PowerProxyChannel { @@ -112,7 +113,9 @@ impl<'a> Psu for PowerProxyDevice<'a> { fn state_mut(&mut self) -> &mut power_policy_interface::psu::State { &mut self.psu_state } +} +impl<'a> Named for PowerProxyDevice<'a> { fn name(&self) -> &'static str { self.name }