Skip to content
Draft
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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions battery-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ embedded-hal-async.workspace = true
embedded-hal.workspace = true
embedded-services.workspace = true
log = { workspace = true, optional = true }
odp-service-common.workspace = true
zerocopy.workspace = true
mctp-rs = { workspace = true, features = ["espi"] }
heapless.workspace = true
Expand Down
162 changes: 125 additions & 37 deletions battery-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use battery_service_messages::{AcpiBatteryError, AcpiBatteryRequest, AcpiBattery
use context::BatteryEvent;
use embedded_services::{
comms::{self, EndpointID},
error, trace,
error, info, trace,
};

mod acpi;
Expand All @@ -15,112 +15,200 @@ pub mod controller;
pub mod device;
#[cfg(feature = "mock")]
pub mod mock;
pub mod task;
pub mod wrapper;

/// Standard Battery Service.
pub struct Service {
pub endpoint: comms::Endpoint,
pub context: context::Context,
/// Parameters required to initialize the battery service.
pub struct InitParams<'hw, const N: usize> {
pub devices: [&'hw device::Device; N],
pub config: context::Config,
}

impl Service {
/// Create a new battery service instance.
pub const fn new() -> Self {
Self::new_inner(context::Config::new())
}

/// Create a new battery service instance with context configuration.
pub const fn new_with_ctx_config(config: context::Config) -> Self {
Self::new_inner(config)
}
/// The main service implementation.
struct ServiceInner<const N: usize> {
endpoint: comms::Endpoint,
context: context::Context,
}

const fn new_inner(config: context::Config) -> Self {
Service {
impl<const N: usize> ServiceInner<N> {
fn new(config: context::Config) -> Self {
Self {
endpoint: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Battery)),
context: context::Context::new_with_config(config),
}
}

/// Main battery service processing function.
pub async fn process_next(&self) {
async fn process_next(&self) {
let event = self.wait_next().await;
self.process_event(event).await
}

/// Wait for next event.
pub async fn wait_next(&self) -> BatteryEvent {
async fn wait_next(&self) -> BatteryEvent {
self.context.wait_event().await
}

/// Process battery service event.
pub async fn process_event(&self, event: BatteryEvent) {
async fn process_event(&self, event: BatteryEvent) {
trace!("Battery service: state machine event recvd {:?}", event);
self.context.process(event).await
}

/// Register fuel gauge device with the battery service.
///
/// Must be done before sending the battery service commands so that hardware device is visible
/// to the battery service.
pub(crate) fn register_fuel_gauge(
fn register_fuel_gauge(
&self,
device: &'static device::Device,
) -> Result<(), embedded_services::intrusive_list::Error> {
self.context.register_fuel_gauge(device)?;

Ok(())
}

/// Use the battery service endpoint to send data to other subsystems and services.
pub async fn comms_send(&self, endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> {
async fn comms_send(&self, endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> {
self.endpoint.send(endpoint_id, data).await
}

/// Send the battery service state machine an event and await a response.
async fn execute_event(&self, event: BatteryEvent) -> context::BatteryResponse {
self.context.execute_event(event).await
}

/// Wait for a response from the battery service.
async fn wait_for_battery_response(&self) -> context::BatteryResponse {
self.context.wait_response().await
}

/// Asynchronously query the state from the state machine.
async fn get_state(&self) -> context::State {
self.context.get_state().await
}
}

/// The memory resources required by the battery service.
#[derive(Default)]
pub struct Resources<const N: usize> {
inner: Option<ServiceInner<N>>,
}

/// A task runner for the battery service. Users of the service must run this object in an embassy task or similar async execution context.
pub struct Runner<'hw, const N: usize> {
service: &'hw ServiceInner<N>,
}

impl<'hw, const N: usize> odp_service_common::runnable_service::ServiceRunner<'hw> for Runner<'hw, N> {
/// Run the service.
async fn run(self) -> embedded_services::Never {
info!("Starting battery-service");
loop {
self.service.process_next().await;
}
}
}

/// Control handle for the battery service. Use this to interact with the battery service.
#[derive(Clone, Copy)]
pub struct Service<'hw, const N: usize> {
inner: &'hw ServiceInner<N>,
}

impl<'hw, const N: usize> Service<'hw, N> {
/// Main battery service processing function.
pub async fn process_next(&self) {
self.inner.process_next().await
}

/// Wait for next event.
pub async fn wait_next(&self) -> BatteryEvent {
self.inner.wait_next().await
}

/// Process battery service event.
pub async fn process_event(&self, event: BatteryEvent) {
self.inner.process_event(event).await
}

/// Use the battery service endpoint to send data to other subsystems and services.
pub async fn comms_send(&self, endpoint_id: EndpointID, data: &(impl Any + Send + Sync)) -> Result<(), Infallible> {
self.inner.comms_send(endpoint_id, data).await
}

/// Send the battery service state machine an event and await a response.
///
/// This is an alternative method of interacting with the battery service (instead of using the comms service),
/// and is a useful fn if you want to send an event and await a response sequentially.
pub async fn execute_event(&self, event: BatteryEvent) -> context::BatteryResponse {
self.context.execute_event(event).await
self.inner.execute_event(event).await
}

/// Wait for a response from the battery service.
///
/// Use this function after sending the battery service a message via the comms system.
pub async fn wait_for_battery_response(&self) -> context::BatteryResponse {
self.context.wait_response().await
self.inner.wait_for_battery_response().await
}

/// Asynchronously query the state from the state machine.
pub async fn get_state(&self) -> context::State {
self.context.get_state().await
self.inner.get_state().await
}
}

impl Default for Service {
fn default() -> Self {
Self::new()
/// Errors that can occur during battery service initialization.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InitError {
DeviceRegistrationFailed,
CommsRegistrationFailed,
}
Comment on lines +156 to +162
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InitError::DeviceRegistrationFailed no longer includes which device(s) failed to register (the previous init path returned the failing DeviceId list). Losing that information makes field debugging harder; consider storing the failing DeviceId (or a bounded Vec) in the error variant.

Copilot uses AI. Check for mistakes.

impl<'hw, const N: usize> odp_service_common::runnable_service::Service<'hw> for Service<'hw, N>
where
'hw: 'static, // TODO relax this 'static requirement when we drop usages of IntrusiveList (including comms)
{
type Runner = Runner<'hw, N>;
type ErrorType = InitError;
type InitParams = InitParams<'hw, N>;
type Resources = Resources<N>;

async fn new(
service_storage: &'hw mut Resources<N>,
init_params: Self::InitParams,
) -> Result<(Self, Runner<'hw, N>), InitError> {
let service = service_storage.inner.insert(ServiceInner::new(init_params.config));

for device in init_params.devices {
if service.register_fuel_gauge(device).is_err() {
error!("Failed to register battery device with DeviceId {:?}", device.id());
return Err(InitError::DeviceRegistrationFailed);
}
}

if comms::register_endpoint(service, &service.endpoint).await.is_err() {
error!("Failed to register battery service endpoint");
return Err(InitError::CommsRegistrationFailed);
}

Ok((Self { inner: service }, Runner { service }))
}
}

impl embedded_services::relay::mctp::RelayServiceHandlerTypes for Service {
impl<const N: usize> embedded_services::relay::mctp::RelayServiceHandlerTypes for Service<'_, N> {
type RequestType = AcpiBatteryRequest;
type ResultType = AcpiBatteryResult;
}

impl embedded_services::relay::mctp::RelayServiceHandler for Service {
impl<const N: usize> embedded_services::relay::mctp::RelayServiceHandler for Service<'_, N> {
async fn process_request(&self, request: Self::RequestType) -> Self::ResultType {
trace!("Battery service: ACPI cmd recvd");
let response = self.context.process_acpi_cmd(&request).await;
let response = self.inner.context.process_acpi_cmd(&request).await;
if let Err(e) = response {
error!("Battery service command failed: {:?}", e)
}
response
}
}

impl comms::MailboxDelegate for Service {
impl<const N: usize> comms::MailboxDelegate for ServiceInner<N> {
fn receive(&self, message: &comms::Message) -> Result<(), comms::MailboxDelegateError> {
if let Some(event) = message.data.get::<BatteryEvent>() {
self.context.send_event_no_wait(*event).map_err(|e| match e {
Expand Down
6 changes: 4 additions & 2 deletions battery-service/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use embedded_batteries_async::{
use embedded_services::{GlobalRawMutex, error, info};

// Convenience fns
pub async fn init_state_machine(battery_service: &'static crate::Service) -> Result<(), crate::context::ContextError> {
pub async fn init_state_machine<const N: usize>(
battery_service: &crate::Service<'_, N>,
) -> Result<(), crate::context::ContextError> {
battery_service
.execute_event(crate::context::BatteryEvent {
event: crate::context::BatteryEventInner::DoInit,
Expand Down Expand Up @@ -34,7 +36,7 @@ pub async fn init_state_machine(battery_service: &'static crate::Service) -> Res
Ok(())
}

pub async fn recover_state_machine(battery_service: &'static crate::Service) -> Result<(), ()> {
pub async fn recover_state_machine<const N: usize>(battery_service: &crate::Service<'_, N>) -> Result<(), ()> {
loop {
match battery_service
.execute_event(crate::context::BatteryEvent {
Expand Down
42 changes: 0 additions & 42 deletions battery-service/src/task.rs

This file was deleted.

10 changes: 10 additions & 0 deletions examples/pico-de-gallo/Cargo.lock

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

1 change: 1 addition & 0 deletions examples/pico-de-gallo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ embassy-futures = "0.1.2"

embedded-batteries-async = "0.3"
battery-service = { path = "../../battery-service", features = ["log"] }
odp-service-common = { path = "../../odp-service-common" }

pico-de-gallo-hal = "0.1.0"

Expand Down
Loading
Loading