Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions power-policy-interface/src/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,72 @@ impl ProviderFlags {
}
}

bitfield! {
/// Flags for disconnect events
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct ConsumerDisconnectRaw(u32);
impl Debug;
/// Renegotiation
///
/// When set this flag indicates that the current consumer is attempting to negotiate a new power capability.
pub bool, renegotiation, set_renegotiation: 0;
/// Switching
///
/// When set this flag indicates that the service is switching to a different PSU.
pub bool, switching, set_switching: 1;
}

/// Type safe wrapper for consumer disconnect flags
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConsumerDisconnect(ConsumerDisconnectRaw);

impl ConsumerDisconnect {
/// Create new consumer disconnect flags with no flags set
pub const fn none() -> Self {
Self(ConsumerDisconnectRaw(0))
}

/// Builder method to set the renegotiation flag
pub fn with_renegotiation(mut self, value: bool) -> Self {
self.set_renegotiation(value);
self
}

/// Set the value of the renegotiation flag
pub fn set_renegotiation(&mut self, value: bool) {
self.0.set_renegotiation(value);
}

/// Get the value of the renegotiation flag
pub fn renegotiation(&self) -> bool {
self.0.renegotiation()
}

/// Builder method to set the switching flag
pub fn with_switching(mut self, value: bool) -> Self {
self.set_switching(value);
self
}

/// Set the value of the switching flag
pub fn set_switching(&mut self, value: bool) {
self.0.set_switching(value);
}

/// Get the value of the switching flag
pub fn switching(&self) -> bool {
self.0.switching()
}
}

impl Default for ConsumerDisconnect {
fn default() -> Self {
Self::none()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -256,4 +322,34 @@ mod tests {
provider.set_psu_type(PsuType::Unknown);
assert_eq!(provider.0.0, 0x0);
}

#[test]
fn test_consumer_disconnect_renegotiation() {
let mut disconnect = ConsumerDisconnect::none().with_renegotiation(true);
assert_eq!(disconnect.0.0, 0x1);
assert!(disconnect.renegotiation());
assert!(!disconnect.switching());
disconnect.set_renegotiation(false);
assert_eq!(disconnect.0.0, 0x0);
assert!(!disconnect.renegotiation());
}

#[test]
fn test_consumer_disconnect_switching() {
let mut disconnect = ConsumerDisconnect::none().with_switching(true);
assert_eq!(disconnect.0.0, 0x2);
assert!(disconnect.switching());
assert!(!disconnect.renegotiation());
disconnect.set_switching(false);
assert_eq!(disconnect.0.0, 0x0);
assert!(!disconnect.switching());
}

#[test]
fn test_consumer_disconnect_default() {
let disconnect = ConsumerDisconnect::default();
assert_eq!(disconnect.0.0, 0x0);
assert!(!disconnect.renegotiation());
assert!(!disconnect.switching());
}
}
4 changes: 2 additions & 2 deletions power-policy-interface/src/psu/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use embedded_services::sync::Lockable;

use crate::{
capability::{ConsumerPowerCapability, ProviderPowerCapability},
capability::{ConsumerDisconnect, ConsumerPowerCapability, ProviderPowerCapability},
psu,
};

Expand All @@ -18,7 +18,7 @@ pub enum EventData {
/// Request the given amount of power to provider
RequestedProviderCapability(Option<ProviderPowerCapability>),
/// Notify that a device cannot consume or provide power anymore
Disconnected,
Disconnected(ConsumerDisconnect),
/// Notify that a device has detached
Detached,
}
Expand Down
8 changes: 4 additions & 4 deletions power-policy-interface/src/service/event.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use embedded_services::sync::Lockable;

use crate::{
capability::{ConsumerPowerCapability, ProviderPowerCapability},
capability::{ConsumerDisconnect, ConsumerPowerCapability, ProviderPowerCapability},
psu::Psu,
service::UnconstrainedState,
};
Expand All @@ -16,7 +16,7 @@ use crate::{
#[non_exhaustive]
pub enum EventData {
/// Consumer disconnected
ConsumerDisconnected,
ConsumerDisconnected(ConsumerDisconnect),
/// Consumer connected
ConsumerConnected(ConsumerPowerCapability),
/// Provider disconnected
Expand All @@ -33,7 +33,7 @@ where
{
fn from(value: Event<'device, PSU>) -> Self {
match value {
Event::ConsumerDisconnected(_) => EventData::ConsumerDisconnected,
Event::ConsumerDisconnected(_, flags) => EventData::ConsumerDisconnected(flags),
Event::ConsumerConnected(_, capability) => EventData::ConsumerConnected(capability),
Event::ProviderDisconnected(_) => EventData::ProviderDisconnected,
Event::ProviderConnected(_, capability) => EventData::ProviderConnected(capability),
Expand All @@ -51,7 +51,7 @@ where
PSU::Inner: Psu,
{
/// Consumer disconnected
ConsumerDisconnected(&'device PSU),
ConsumerDisconnected(&'device PSU, ConsumerDisconnect),
/// Consumer connected
ConsumerConnected(&'device PSU, ConsumerPowerCapability),
/// Provider disconnected
Expand Down
27 changes: 23 additions & 4 deletions power-policy-service/src/service/consumer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use super::*;

use power_policy_interface::psu;
use power_policy_interface::service::event::Event as ServiceEvent;
use power_policy_interface::{capability::ConsumerPowerCapability, psu::PsuState};
use power_policy_interface::{
capability::{ConsumerDisconnect, ConsumerPowerCapability},
psu::PsuState,
};

/// State of the current consumer
#[derive(Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -216,7 +219,15 @@ impl<'device, Reg: Registration<'device>, Customization: customization::Customiz
// so just continue execution.
self.disconnect_chargers().await?;

self.broadcast_event(ServiceEvent::ConsumerDisconnected(current_consumer.psu));
// Indicate why the current consumer is being disconnected. If we are reconnecting
// the same device, it is renegotiating a new power capability. Otherwise, the service
// is switching to a different PSU.
let flags = if ptr::eq(current_consumer.psu, new_consumer.psu) {
ConsumerDisconnect::none().with_renegotiation(true)
} else {
ConsumerDisconnect::none().with_switching(true)
};
self.broadcast_event(ServiceEvent::ConsumerDisconnected(current_consumer.psu, flags));

// Don't update the unconstrained here because this is a transitional state
}
Expand All @@ -238,7 +249,12 @@ impl<'device, Reg: Registration<'device>, Customization: customization::Customiz
}

/// Determines and connects the best external power
pub(super) async fn update_current_consumer(&mut self) -> Result<(), Error> {
///
/// `disconnect_flags` describes the reason for a disconnect and is applied to the
/// [`ServiceEvent::ConsumerDisconnected`] event when the current consumer is removed and not
/// replaced by another one. When switching between consumers the flags are derived from the
/// switch itself (see [`Self::connect_new_consumer`]).
pub(super) async fn update_current_consumer(&mut self, disconnect_flags: ConsumerDisconnect) -> Result<(), Error> {
let current_consumer_name = if let Some(current_consumer) = self.state.current_consumer_state {
current_consumer.psu.lock().await.name()
} else {
Expand All @@ -262,7 +278,10 @@ impl<'device, Reg: Registration<'device>, Customization: customization::Customiz
// Notify disconnect if recently detached consumer was previously attached.
if let Some(current_consumer) = self.state.current_consumer_state {
self.disconnect_chargers().await?;
self.broadcast_event(ServiceEvent::ConsumerDisconnected(current_consumer.psu));
self.broadcast_event(ServiceEvent::ConsumerDisconnected(
current_consumer.psu,
disconnect_flags,
));
}
// No new consumer available
self.state.current_consumer_state = None;
Expand Down
16 changes: 10 additions & 6 deletions power-policy-service/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use embedded_services::{event::NonBlockingSender, info, sync::Lockable, trace};

use power_policy_interface::charger::{Charger, PsuState};
use power_policy_interface::{
capability::{ConsumerPowerCapability, ProviderPowerCapability},
capability::{ConsumerDisconnect, ConsumerPowerCapability, ProviderPowerCapability},
charger::{Event as ChargerEvent, EventData as ChargerEventData},
psu::{
Error, Psu,
Expand Down Expand Up @@ -117,7 +117,7 @@ impl<'device, Reg: Registration<'device>, Customization: customization::Customiz
async fn process_notify_detach(&mut self, device: &'device Reg::Psu) -> Result<(), Error> {
info!("({}): Received notify detached", device.lock().await.name());
self.post_provider_removed(device).await;
self.update_current_consumer().await?;
self.update_current_consumer(ConsumerDisconnect::none()).await?;
Ok(())
}

Expand All @@ -132,7 +132,7 @@ impl<'device, Reg: Registration<'device>, Customization: customization::Customiz
capability,
);

self.update_current_consumer().await
self.update_current_consumer(ConsumerDisconnect::none()).await
}

async fn process_request_provider_power_capabilities(
Expand All @@ -149,10 +149,14 @@ impl<'device, Reg: Registration<'device>, Customization: customization::Customiz
self.connect_provider(requester).await
}

async fn process_notify_disconnect(&mut self, device: &'device Reg::Psu) -> Result<(), Error> {
async fn process_notify_disconnect(
&mut self,
device: &'device Reg::Psu,
flags: ConsumerDisconnect,
) -> Result<(), Error> {
info!("({}): Received notify disconnect", device.lock().await.name());
self.post_provider_removed(device).await;
self.update_current_consumer().await?;
self.update_current_consumer(flags).await?;
Ok(())
}

Expand Down Expand Up @@ -180,7 +184,7 @@ impl<'device, Reg: Registration<'device>, Customization: customization::Customiz
self.process_request_provider_power_capabilities(device, capability)
.await
}
PsuEventData::Disconnected => self.process_notify_disconnect(device).await,
PsuEventData::Disconnected(flags) => self.process_notify_disconnect(device, flags).await,
_ => {
info!(
"Received unknown PSU event from ({}): {:?}",
Expand Down
16 changes: 14 additions & 2 deletions power-policy-service/tests/common/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use embassy_sync::{channel, mutex::Mutex, signal::Signal};
use embedded_batteries_async::charger::{MilliAmps, MilliVolts};
use embedded_services::{GlobalRawMutex, event::NonBlockingSender, info, named::Named};
use power_policy_interface::{
capability::{ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability},
capability::{
ConsumerDisconnect, ConsumerPowerCapability, PowerCapability, ProviderFlags, ProviderPowerCapability,
},
charger,
psu::{Error, Psu, State, event::EventData},
};
Expand Down Expand Up @@ -54,6 +56,14 @@ impl<'a, S: NonBlockingSender<EventData>> Mock<'a, S> {
.unwrap();
}

/// Simulate an already-attached consumer renegotiating a new power capability.
pub async fn simulate_update_consumer_power_capability(&mut self, capability: Option<ConsumerPowerCapability>) {
self.state.update_consumer_power_capability(capability).unwrap();
self.sender
.try_send(EventData::UpdatedConsumerCapability(capability))
.unwrap();
}

pub async fn simulate_detach(&mut self) {
self.state.detach();
self.sender.try_send(EventData::Detached).unwrap();
Expand All @@ -77,7 +87,9 @@ impl<'a, S: NonBlockingSender<EventData>> Mock<'a, S> {

pub async fn simulate_disconnect(&mut self) {
self.state.disconnect(true).unwrap();
self.sender.try_send(EventData::Disconnected).unwrap();
self.sender
.try_send(EventData::Disconnected(ConsumerDisconnect::none()))
.unwrap();
}

pub async fn simulate_update_requested_provider_power_capability(
Expand Down
16 changes: 14 additions & 2 deletions power-policy-service/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use embassy_time::{Duration, with_timeout};
use embedded_services::GlobalRawMutex;
use power_policy_interface::psu::event::EventData;
use power_policy_interface::{
capability::{ConsumerPowerCapability, PowerCapability, ProviderPowerCapability},
capability::{ConsumerDisconnect, ConsumerPowerCapability, PowerCapability, ProviderPowerCapability},
service::{UnconstrainedState, event::Event as ServiceEvent},
};
use power_policy_service::service::{Service, config::Config, customization};
Expand Down Expand Up @@ -168,12 +168,24 @@ 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 {
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_disconnected_with_flags<'a>(
receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>,
expected_device: &DeviceType<'a>,
expected_flags: ConsumerDisconnect,
) {
let ServiceEvent::ConsumerDisconnected(device, flags) = receiver.receive().await else {
panic!("Expected ConsumerDisconnected event");
};
assert_eq!(device as *const _, expected_device as *const _);
assert_eq!(flags, expected_flags);
}

pub async fn assert_consumer_connected<'a>(
receiver: DynamicReceiver<'a, ServiceEvent<'a, DeviceType<'a>>>,
expected_device: &DeviceType<'a>,
Expand Down
Loading
Loading