OpenDevicePartnership / OpenDevicePartnership/embedded-sensors

Add power meter sensor traits (VoltageSensor, CurrentSensor, PowerSensor, EnergySensor)

Open
#43 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
4
Forks
9
Avg merge
5d 13h
Merged PRs (30d)
3

Description

While developing the INA4230 quad-channel power monitor driver for ODP, we found that embedded-sensors lacks traits for power measurement — voltage, current, power, and energy. These are as fundamental to embedded systems as temperature and humidity, and belong alongside TemperatureSensor and HumiditySensor in embedded-sensors.

Power monitor ICs would be an important part of the ODP platform — they monitor power rails, and system power and energy consumption. Having standardized traits would allow the embedded-services power monitoring service to be generic over any power monitor IC, just as the thermal service is generic over any temperature sensor.

Proposed Traits
We propose traits that are generic over a Channel type defined by each implementation:

/// Async voltage sensor.
pub trait VoltageSensor: ErrorType {
    /// The channel type for this sensor.
    /// Single-channel devices may use a single-variant enum.
    /// Multi-channel devices define an enum with one variant per channel.
    type Channel;

   /// Read the bus voltage in millivolts.
    async fn bus_voltage(
        &mut self,
        channel: Self::Channel,
    ) -> Result<MilliVolts, Self::Error>;

    /// Read the shunt voltage in millivolts.
    async fn shunt_voltage(
        &mut self,
        channel: Self::Channel,
    ) -> Result<MilliVolts, Self::Error>;
}

/// Async current sensor.
pub trait CurrentSensor: ErrorType {
    /// The channel type for this sensor.
    type Channel;

    /// Read the calculated current in milliamperes.
    /// Requires calibration to have been performed first.
    async fn current(
        &mut self,
        channel: Self::Channel,
    ) -> Result<MilliAmps, Self::Error>;
}

/// Async power sensor.
pub trait PowerSensor: ErrorType {
    /// The channel type for this sensor.
    type Channel;

    /// Read the calculated power in milliwatts.
    /// Requires calibration to have been performed first.
    async fn power(
        &mut self,
        channel: Self::Channel,
    ) -> Result<MilliWatts, Self::Error>;
}

/// Async energy sensor.
pub trait EnergySensor: ErrorType {
    /// The channel type for this sensor.
    type Channel;

    /// Read the accumulated energy in millijoules.
    /// Requires calibration to have been performed first.
    async fn energy(
        &mut self,
        channel: Self::Channel,
    ) -> Result<MilliJoules, Self::Error>;
}

Trait Composability
The four traits are intentionally separate and independent. Not all power monitor ICs implement all four measurements. A device only implements the traits that match its hardware capabilities:

// Single channel device — voltage and current only
impl VoltageSensor for SimpleMonitor {
    type Channel = Channel;
    async fn bus_voltage(&mut self, _ch: Channel) -> Result<MilliVolts, Self::Error> {
        self.read_vbus().await
    }
    async fn shunt_voltage(&mut self, _ch: Channel) -> Result<MilliVolts, Self::Error> {
        self.read_vshunt().await
    }
}

impl CurrentSensor for SimpleMonitor {
    type Channel = Channel;
    async fn current(&mut self, _ch: Channel) -> Result<MilliAmps, Self::Error> {
        self.read_current().await
    }
}

// Multi-channel device with full power and energy monitoring
impl VoltageSensor for FullPowerMonitor {
    type Channel = Channel; // Ch1, Ch2, Ch3, Ch4
    async fn bus_voltage(&mut self, channel: Channel) -> Result<MilliVolts, Self::Error> {
        match channel {
            Channel::Ch1 => ...
            Channel::Ch2 => ...
        }
    }
    async fn shunt_voltage(&mut self, channel: Channel) -> Result<MilliVolts, Self::Error> {
        ...
    }
}

impl CurrentSensor for FullPowerMonitor { ... }
impl PowerSensor for FullPowerMonitor { ... }
impl EnergySensor for FullPowerMonitor { ... }

Real World Device Capabilities
The trait split reflects real hardware differences across the industry. Different power monitor ICs from various vendors implement different subsets of these measurements:
Image

This demonstrates that the trait separation reflects industry-wide hardware differences and that these traits would be valuable across the entire embedded ecosystem regardless of vendor.
Physical Units
We propose adding the following type aliases to embedded-sensors:

/// Voltage in millivolts.
pub type MilliVolts = f32;
/// Current in milliamperes.
pub type MilliAmps = f32;
/// Power in milliwatts.
pub type MilliWatts = f32;
/// Energy in millijoules.
pub type MilliJoules = u32;  

Voltage, current, and power use f32 as they are instantaneous measurements with bounded ranges that fit comfortably within f32 precision.
Energy uses u32 for the following reasons:
Hardware aligned — many power monitor ICs implement energy as an unsigned integer accumulator register. u32 is a practical choice that matches common hardware implementations such as the INA4230's 32-bit energy register, while avoiding the overhead of larger integer types.
No 64-bit overhead — on Cortex-M0+ (no 64-bit ALU), u64 arithmetic requires software emulation with multiple instructions. u32 operations are native and efficient
Sufficient for typical use — applications are expected to periodically reset the energy accumulator via the driver's reset function. Long-term energy tracking across resets should be handled at the application layer if needed

Reference Implementation
The INA4230 ODP driver currently defines these traits locally as an interim measure. Once upstreamed to embedded-sensors, the INA4230 driver would be updated to depend on the shared traits, serving as the reference implementation.
Relation to embedded-services
Once these traits are in embedded-sensors, an embedded-services power monitoring service could be built that is generic over any combination of these traits, similar to how the thermal service works with TemperatureSensor. This would allow any compliant power monitor driver to be dropped into an ODP platform without changes to the service layer.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing TemperatureSensor and HumiditySensor traits, then compare them with the INA4230 driver's local interim traits described in the issue. Define the shared voltage, current, power, and energy traits and physical-unit aliases, then update the INA4230 driver to use them. Done means the traits support the stated channel and composability requirements.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
embedded-iot
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.