Initial node implementation (#4)
Reviewed-on: #4 Co-authored-by: Zack <zack@bfpower.io> Co-committed-by: Zack <zack@bfpower.io>
This commit is contained in:
20
src/cell.rs
Normal file
20
src/cell.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use core::cell::Cell;
|
||||
|
||||
/// Provides a view only reference to a [Cell].
|
||||
/// Useful alternative to `&Cell` when an API wants to control where a [Cell]s value can be set.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct CellView<'a, T: Copy>(&'a Cell<T>);
|
||||
|
||||
impl<T: Copy> CellView<'_, T> {
|
||||
#[inline(always)]
|
||||
pub fn get(self) -> T {
|
||||
self.0.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Copy> From<&'a Cell<T>> for CellView<'a, T> {
|
||||
#[inline(always)]
|
||||
fn from(value: &'a Cell<T>) -> Self {
|
||||
CellView(value)
|
||||
}
|
||||
}
|
41
src/error.rs
Normal file
41
src/error.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use crate::transducer::InvalidValue;
|
||||
|
||||
/// An error that it is likely impossible to recover from. This error should only be created in
|
||||
/// situations where attempts to recover have already been attempted and have failed. Error handling
|
||||
/// should consist of attempting to alert another system for maintenance and attempting to shut down
|
||||
/// any systems that depend on the correct functionality of the component having errors.
|
||||
///
|
||||
/// In many systems there can be a single function for handling any critical error as a critical
|
||||
/// error always means everything needs to be stopped.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum CriticalError {
|
||||
/// Critical communication failed and retries are either impossible or also failed.
|
||||
Communication,
|
||||
InvalidValue(InvalidValue),
|
||||
}
|
||||
|
||||
impl CriticalError {
|
||||
pub fn emergency_procedure(self, procedure: impl FnOnce(CriticalError) -> !) -> ! {
|
||||
procedure(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// [Result] where error type is [CriticalError].
|
||||
pub trait CriticalErrResult: Copy {
|
||||
type Value: Copy;
|
||||
|
||||
/// Execute emergency procedure in the event of a critical, the emergency procedure cannot
|
||||
/// return. It should usually terminate the program, potentially rebooting the device in some sort of recovery mode.
|
||||
fn err_emproc(self, procedure: impl FnOnce(CriticalError) -> !) -> Self::Value;
|
||||
}
|
||||
|
||||
impl<T: Copy> CriticalErrResult for Result<T, CriticalError> {
|
||||
type Value = T;
|
||||
|
||||
fn err_emproc(self, procedure: impl FnOnce(CriticalError) -> !) -> Self::Value {
|
||||
match self {
|
||||
Ok(val) => val,
|
||||
Err(error) => error.emergency_procedure(procedure),
|
||||
}
|
||||
}
|
||||
}
|
8
src/lib.rs
Normal file
8
src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![no_std]
|
||||
#![feature(async_fn_in_trait, never_type)]
|
||||
|
||||
pub mod transducer;
|
||||
pub mod cell;
|
||||
mod error;
|
||||
|
||||
pub use error::CriticalError;
|
6
src/transducer/input.rs
Normal file
6
src/transducer/input.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub trait Poll {
|
||||
type Value: Copy;
|
||||
type Error: Copy;
|
||||
|
||||
async fn poll(&self) -> Result<Self::Value, Self::Error>;
|
||||
}
|
62
src/transducer/mod.rs
Normal file
62
src/transducer/mod.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use core::cell::Cell;
|
||||
use crate::cell::CellView;
|
||||
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
mod part;
|
||||
|
||||
pub use part::*;
|
||||
|
||||
// Initialisation will always be async and won't complete until a state is available for all
|
||||
// stateful transducers.
|
||||
pub trait Stateful {
|
||||
type Value: Copy;
|
||||
|
||||
fn state_cell(&self) -> CellView<Self::Value>;
|
||||
|
||||
fn state(&self) -> Self::Value;
|
||||
}
|
||||
|
||||
pub struct State<T: Copy> {
|
||||
state_cell: Cell<T>,
|
||||
}
|
||||
|
||||
impl<T: Copy> State<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(state_cell: Cell<T>) -> Self {
|
||||
Self { state_cell }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update(&self, value: T) {
|
||||
self.state_cell.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Stateful for State<T> {
|
||||
type Value = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn state_cell(&self) -> CellView<Self::Value> {
|
||||
(&self.state_cell).into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn state(&self) -> Self::Value {
|
||||
self.state_cell.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> From<Cell<T>> for State<T> {
|
||||
#[inline(always)]
|
||||
fn from(state_cell: Cell<T>) -> Self {
|
||||
State::new(state_cell)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Error ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
/// Indicates the transducer value is statically known to be impossible.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InvalidValue;
|
8
src/transducer/output.rs
Normal file
8
src/transducer/output.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use crate::transducer::InvalidValue;
|
||||
|
||||
pub trait Output {
|
||||
type Value: Copy;
|
||||
|
||||
//TODO: Should this be maybe async?
|
||||
fn output(&mut self, setting: Self::Value) -> Result<(), InvalidValue>;
|
||||
}
|
22
src/transducer/part/lm35.rs
Normal file
22
src/transducer/part/lm35.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use crate::transducer::InvalidValue;
|
||||
use uom::si::electric_potential::volt;
|
||||
use uom::si::f32;
|
||||
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
const MIN_VOLTS: f32 = -0.55;
|
||||
const MAX_VOLTS: f32 = 1.50;
|
||||
const SCALE_FACTOR: f32 = 100.0;
|
||||
|
||||
#[inline]
|
||||
pub fn convert(
|
||||
voltage: f32::ElectricPotential,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let volts = voltage.get::<volt>();
|
||||
|
||||
if volts >= MIN_VOLTS && volts <= MAX_VOLTS {
|
||||
let celsius = volts * SCALE_FACTOR;
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
7
src/transducer/part/mod.rs
Normal file
7
src/transducer/part/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod thermocouple;
|
||||
|
||||
#[cfg(feature = "lm35")]
|
||||
pub mod lm35;
|
||||
|
||||
#[cfg(feature = "thermocouple_k")]
|
||||
pub use thermocouple::type_k as thermocouple_k;
|
2
src/transducer/part/thermocouple/mod.rs
Normal file
2
src/transducer/part/thermocouple/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "thermocouple_k")]
|
||||
pub mod type_k;
|
173
src/transducer/part/thermocouple/type_k.rs
Normal file
173
src/transducer/part/thermocouple/type_k.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use crate::transducer::InvalidValue;
|
||||
use libm::powf;
|
||||
use uom::si::electric_potential::{millivolt, volt};
|
||||
use uom::si::f32;
|
||||
use uom::si::thermodynamic_temperature::degree_celsius;
|
||||
|
||||
fn _convert(
|
||||
voltage: f32::ElectricPotential,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let mv = voltage.get::<millivolt>();
|
||||
let mv_pow2 = mv * mv;
|
||||
let mv_pow3 = mv_pow2 * mv;
|
||||
let mv_pow4 = mv_pow3 * mv;
|
||||
let mv_pow5 = mv_pow4 * mv;
|
||||
let mv_pow6 = mv_pow5 * mv;
|
||||
|
||||
if mv >= -5.891 && mv <= 0.0 {
|
||||
let mv_pow7 = mv_pow6 * mv;
|
||||
let mv_pow8 = mv_pow7 * mv;
|
||||
|
||||
let celsius = 2.5173462E+1 * mv
|
||||
+ -1.1662878 * mv_pow2
|
||||
+ -1.0833638 * mv_pow3
|
||||
+ -8.9773540E-1 * mv_pow4
|
||||
+ -3.7342377E-1 * mv_pow5
|
||||
+ -8.6632643E-2 * mv_pow6
|
||||
+ -1.0450598E-2 * mv_pow7
|
||||
+ -5.1920577E-4 * mv_pow8;
|
||||
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else if mv > 0.0 && mv < 20.644 {
|
||||
let mv_pow7 = mv_pow6 * mv;
|
||||
let mv_pow8 = mv_pow7 * mv;
|
||||
let mv_pow9 = mv_pow8 * mv;
|
||||
|
||||
let celsius = 2.508355E+1 * mv
|
||||
+ 7.860106E-2 * mv_pow2
|
||||
+ -2.503131E-1 * mv_pow3
|
||||
+ 8.315270E-2 * mv_pow4
|
||||
+ -1.228034E-2 * mv_pow5
|
||||
+ 9.804036E-4 * mv_pow6
|
||||
+ -4.413030E-5 * mv_pow7
|
||||
+ 1.057734E-6 * mv_pow8
|
||||
+ -1.052755E-8 * mv_pow9;
|
||||
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else if mv >= 20.644 && mv <= 54.886 {
|
||||
let celsius = 1.318058e2
|
||||
+ 4.830222E+1 * mv
|
||||
+ -1.646031 * mv_pow2
|
||||
+ 5.464731E-2 * mv_pow3
|
||||
+ -9.650715E-4 * mv_pow4
|
||||
+ 8.802193E-6 * mv_pow5
|
||||
+ -3.110810E-8 * mv_pow6;
|
||||
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(celsius))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
/// directly adding the reference junction temperature to the result for offset compensation.
|
||||
///
|
||||
/// Can be useful compared to [convert_seebeck] when the reference temperature or the temperature
|
||||
/// being read by the thermocouple is fairly close to 0.
|
||||
///
|
||||
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_direct(
|
||||
voltage: f32::ElectricPotential,
|
||||
r_junction: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let base_celsius = _convert(voltage)?.get::<degree_celsius>();
|
||||
let r_junction_celsius = r_junction.get::<degree_celsius>();
|
||||
|
||||
Ok(f32::ThermodynamicTemperature::new::<degree_celsius>(base_celsius + r_junction_celsius))
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
/// using a constant seebeck coefficient to correct the input voltage for offset compensation.
|
||||
///
|
||||
/// Probably the right choice most of the time.
|
||||
///
|
||||
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_seebeck(
|
||||
voltage: f32::ElectricPotential,
|
||||
r_junction: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let voltage_correction = temp_to_voltage_seebeck(r_junction)?;
|
||||
_convert(voltage + voltage_correction)
|
||||
}
|
||||
|
||||
/// Convert from a voltage produced by a type k thermocouple to a temperature using polynomial and
|
||||
/// using a polynomial to correct the input voltage for offset compensation.
|
||||
///
|
||||
/// This is the most accurate method but uses the most processor cycles by a wide margin.
|
||||
///
|
||||
/// This function uses the [NIST type K thermocouple linearisation polynomial](https://srdata.nist.gov/its90/type_k/kcoefficients_inverse.html).
|
||||
#[inline]
|
||||
pub fn convert_polynomial(
|
||||
voltage: f32::ElectricPotential,
|
||||
r_junction: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ThermodynamicTemperature, InvalidValue> {
|
||||
let voltage_correction = temp_to_voltage_poly(r_junction)?;
|
||||
_convert(voltage + voltage_correction)
|
||||
}
|
||||
|
||||
//TODO: This is not working, check libm pow.
|
||||
pub fn temp_to_voltage_poly(
|
||||
temperature: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ElectricPotential, InvalidValue> {
|
||||
let celsius = temperature.get::<degree_celsius>();
|
||||
let cel_pow2 = celsius * celsius;
|
||||
let cel_pow3 = cel_pow2 * celsius;
|
||||
let cel_pow4 = cel_pow3 * celsius;
|
||||
let cel_pow5 = cel_pow4 * celsius;
|
||||
let cel_pow6 = cel_pow5 * celsius;
|
||||
let cel_pow7 = cel_pow6 * celsius;
|
||||
let cel_pow8 = cel_pow7 * celsius;
|
||||
let cel_pow9 = cel_pow8 * celsius;
|
||||
|
||||
if celsius >= -270.0 && celsius < 0.0 {
|
||||
let cel_pow10 = cel_pow9 * celsius;
|
||||
|
||||
let mv = 0.394501280250E-01 * celsius
|
||||
+ 0.236223735980E-04 * cel_pow2
|
||||
+ -0.328589067840E-06 * cel_pow3
|
||||
+ -0.499048287770E-08 * cel_pow4
|
||||
+ -0.675090591730E-10 * cel_pow5
|
||||
+ -0.574103274280E-12 * cel_pow6
|
||||
+ -0.310888728940E-14 * cel_pow7
|
||||
+ -0.104516093650E-16 * cel_pow8
|
||||
+ -0.198892668780E-19 * cel_pow9
|
||||
+ -0.163226974860E-22 * cel_pow10;
|
||||
|
||||
Ok(f32::ElectricPotential::new::<millivolt>(mv))
|
||||
} else if celsius >= 0.0 && celsius <= 1372.0 {
|
||||
let base = celsius - 0.126968600000E+03;
|
||||
let exp = -0.118343200000E-03 * (base * base);
|
||||
let addition = powf(0.1185976, exp);
|
||||
|
||||
let mv = -0.176004136860E-01
|
||||
+ 0.389212049750E-01 * celsius
|
||||
+ 0.185587700320E-04 * cel_pow2
|
||||
+ -0.994575928740E-07 * cel_pow3
|
||||
+ 0.318409457190E-09 * cel_pow4
|
||||
+ -0.560728448890E-12 * cel_pow5
|
||||
+ 0.560750590590E-15 * cel_pow6
|
||||
+ -0.320207200030E-18 * cel_pow7
|
||||
+ 0.971511471520E-22 * cel_pow8
|
||||
+ -0.121047212750E-25 * cel_pow9
|
||||
+ addition;
|
||||
|
||||
Ok(f32::ElectricPotential::new::<millivolt>(mv))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn temp_to_voltage_seebeck(
|
||||
temperature: f32::ThermodynamicTemperature,
|
||||
) -> Result<f32::ElectricPotential, InvalidValue> {
|
||||
let celsius = temperature.get::<degree_celsius>();
|
||||
if celsius >= -2.0 && celsius <= 800.0 {
|
||||
let mv = 0.041 * celsius;
|
||||
Ok(f32::ElectricPotential::new::<millivolt>(mv))
|
||||
} else {
|
||||
Err(InvalidValue)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user