Skip to content
Merged
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
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions battery-service-interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ ignored = ["log"]
defmt = { workspace = true, optional = true }
log = { workspace = true, optional = true }
embedded-batteries-async.workspace = true
power-policy-interface.workspace = true

[lints]
workspace = true

[features]
defmt = ["dep:defmt", "embedded-batteries-async/defmt"]
log = ["dep:log"]
defmt = [
"dep:defmt",
"embedded-batteries-async/defmt",
"power-policy-interface/defmt",
]
log = ["dep:log", "power-policy-interface/log"]
44 changes: 41 additions & 3 deletions battery-service-interface/src/fuel_gauge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use embedded_batteries_async::{
MilliAmpsSigned, Minutes, Percent,
},
};
pub use power_policy_interface::psu::PsuState;

/// Fuel gauge errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -381,9 +382,10 @@ pub enum InternalState {

/// Fuel gauge state, owned by the driver (OEM) and managed via the `on_*` transition methods.
///
/// This holds both the fuel gauge state machine state and the cached static and
/// dynamic battery data. The battery service reads this state (via
/// [`FuelGauge::state`]) when answering ACPI queries.
/// This holds the fuel gauge's internal state, cached static and dynamic battery
/// data, and PSU state from routed power-policy notifications. It keeps no PSU
/// reference. The battery service reads this state through
/// [`FuelGauge::state`] to answer ACPI queries.
///
/// The cached data types are generic and default to the standard
/// [`StaticBatteryMsgs`] / [`DynamicBatteryMsgs`]. OEMs may substitute custom
Expand All @@ -394,6 +396,8 @@ pub struct State<S: StaticBatteryData = StaticBatteryMsgs, D: DynamicBatteryData
state: InternalState,
static_cache: S,
dynamic_cache: D,
psu_state: PsuState,
psu_unconstrained: bool,
}

impl<S: StaticBatteryData, D: DynamicBatteryData> State<S, D> {
Expand Down Expand Up @@ -422,6 +426,40 @@ impl<S: StaticBatteryData, D: DynamicBatteryData> State<S, D> {
&mut self.dynamic_cache
}

/// The cached state of this gauge's external power source.
///
/// Defaults to [`PsuState::Detached`] until a state is reported.
/// [`PsuState::Idle`] means the source is attached but not supplying power.
/// Unconstrained power is tracked separately by [`Self::is_psu_unconstrained`].
pub fn psu_state(&self) -> PsuState {
self.psu_state
}

/// Update external power state from a routed notification.
///
/// Refreshes the unconstrained flag from the consumer capability, or clears it
/// for other states.
/// Leaves the fuel gauge's presence and operational state unchanged.
pub fn set_psu_state(&mut self, state: PsuState) {
self.psu_state = state;
self.psu_unconstrained =
matches!(state, PsuState::ConnectedConsumer(capability) if capability.flags.unconstrained_power);
}

/// Whether the cached external source is unconstrained, i.e. outlet-powered rather than
/// battery-powered. This says nothing about its wattage.
pub fn is_psu_unconstrained(&self) -> bool {
self.psu_unconstrained
}

/// Set the unconstrained override without changing the cached PSU state.
///
/// While set, `_PSR` reports online. Clearing it restores the status implied by the
/// cached PSU state.
pub fn set_psu_unconstrained(&mut self, unconstrained: bool) {
self.psu_unconstrained = unconstrained;
}

/// Returns `true` if the fuel gauge is present.
pub fn is_present(&self) -> bool {
matches!(self.state, InternalState::Present(_))
Expand Down
123 changes: 123 additions & 0 deletions battery-service-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub struct PifFixedStrings {
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DeviceId(pub u8);

/// Host-facing battery operations.
pub trait BatteryService {
/// Queries the estimated time remaining until the battery reaches the specified charge level. Corresponds to ACPI's _BCT method
fn battery_charge_time(
Expand Down Expand Up @@ -177,12 +178,20 @@ pub trait BatteryService {
) -> impl core::future::Future<Output = Result<(), BatteryError>>;

/// Queries whether the power supply unit is currently in use (i.e., providing power to the system). Corresponds to ACPI's _PSR method.
///
/// The query reads the cached external-power state of the fuel gauge selected by `power_source_id`.
/// A cached `ConnectedConsumer` state or unconstrained-power override reports online.
/// Other sources report offline. An unregistered ID returns an error.
fn is_psu_in_use(
&self,
power_source_id: DeviceId,
) -> impl core::future::Future<Output = Result<PsrReturn, BatteryError>>;

/// Queries information about the battery's power source. Corresponds to ACPI's _PIF method.
///
/// Output and input ratings are unavailable (`0xFFFFFFFF`). ACPI defines them as the source's
/// rated maximums, but power-policy notifications carry only the negotiated contract.
/// An unregistered `power_source_id` returns an error.
fn power_source_information(
&self,
power_source_id: DeviceId,
Expand All @@ -195,6 +204,120 @@ pub trait BatteryService {
) -> impl core::future::Future<Output = Result<StaReturn, BatteryError>>;
}

impl<T: BatteryService + ?Sized> BatteryService for &T {
fn battery_charge_time(
&self,
battery_id: DeviceId,
charge_level: Bct,
) -> impl core::future::Future<Output = Result<BctReturnResult, BatteryError>> {
T::battery_charge_time(*self, battery_id, charge_level)
}

fn battery_info(
&self,
battery_id: DeviceId,
) -> impl core::future::Future<Output = Result<BixFixedStrings, BatteryError>> {
T::battery_info(*self, battery_id)
}

fn set_battery_measurement_averaging_interval(
&self,
battery_id: DeviceId,
bma: Bma,
) -> impl core::future::Future<Output = Result<(), BatteryError>> {
T::set_battery_measurement_averaging_interval(*self, battery_id, bma)
}

fn battery_maintenance_control(
&self,
battery_id: DeviceId,
bmc: Bmc,
) -> impl core::future::Future<Output = Result<(), BatteryError>> {
T::battery_maintenance_control(*self, battery_id, bmc)
}

fn battery_maintenance_data(
&self,
battery_id: DeviceId,
) -> impl core::future::Future<Output = Result<Bmd, BatteryError>> {
T::battery_maintenance_data(*self, battery_id)
}

fn set_battery_measurement_sampling_time(
&self,
battery_id: DeviceId,
battery_measurement_sampling: Bms,
) -> impl core::future::Future<Output = Result<(), BatteryError>> {
T::set_battery_measurement_sampling_time(*self, battery_id, battery_measurement_sampling)
}

fn battery_power_characteristics(
&self,
battery_id: DeviceId,
) -> impl core::future::Future<Output = Result<Bpc, BatteryError>> {
T::battery_power_characteristics(*self, battery_id)
}

fn battery_power_state(
&self,
battery_id: DeviceId,
) -> impl core::future::Future<Output = Result<Bps, BatteryError>> {
T::battery_power_state(*self, battery_id)
}

fn set_battery_power_threshold(
&self,
battery_id: DeviceId,
power_threshold: Bpt,
) -> impl core::future::Future<Output = Result<(), BatteryError>> {
T::set_battery_power_threshold(*self, battery_id, power_threshold)
}

fn battery_status(
&self,
battery_id: DeviceId,
) -> impl core::future::Future<Output = Result<BstReturn, BatteryError>> {
T::battery_status(*self, battery_id)
}

fn battery_time_to_empty(
&self,
battery_id: DeviceId,
battery_discharge_rate: Btm,
) -> impl core::future::Future<Output = Result<BtmReturnResult, BatteryError>> {
T::battery_time_to_empty(*self, battery_id, battery_discharge_rate)
}

fn set_battery_trip_point(
&self,
battery_id: DeviceId,
btp: Btp,
) -> impl core::future::Future<Output = Result<(), BatteryError>> {
T::set_battery_trip_point(*self, battery_id, btp)
}

fn is_psu_in_use(
&self,
power_source_id: DeviceId,
) -> impl core::future::Future<Output = Result<PsrReturn, BatteryError>> {
T::is_psu_in_use(*self, power_source_id)
}

fn power_source_information(
&self,
power_source_id: DeviceId,
) -> impl core::future::Future<Output = Result<PifFixedStrings, BatteryError>> {
T::power_source_information(*self, power_source_id)
}

fn device_status(
&self,
battery_id: DeviceId,
) -> impl core::future::Future<Output = Result<StaReturn, BatteryError>> {
T::device_status(*self, battery_id)
}
}

#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Errors that can occur when interacting with the battery service.
Expand Down
2 changes: 2 additions & 0 deletions battery-service-relay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod serialization;
pub use serialization::{AcpiBatteryError, AcpiBatteryRequest, AcpiBatteryResponse, AcpiBatteryResult};

/// Relays messages to and from a battery service implementation over MCTP.
///
/// The handler accepts shared references as well as owned services:
pub struct BatteryServiceRelayHandler<S: battery_service_interface::BatteryService> {
service: S,
}
Expand Down
5 changes: 5 additions & 0 deletions battery-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ workspace = true
[dependencies]
defmt = { workspace = true, optional = true }
battery-service-interface.workspace = true
embassy-futures.workspace = true
embassy-time.workspace = true
embedded-batteries-async.workspace = true
embedded-services.workspace = true
log = { workspace = true, optional = true }
power-policy-interface.workspace = true

[dev-dependencies]
embassy-sync.workspace = true
power-policy-interface-test-mocks.workspace = true

[features]
default = []
defmt = [
Expand Down
Loading
Loading