input_pipeline/light_sensor/
types.rsuse anyhow::{bail, format_err, Context, Error};
use async_trait::async_trait;
use futures::{Future, FutureExt as _, TryFutureExt as _};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::ops::{Add, Div, Mul, Sub};
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Rgbc<T> {
pub(crate) red: T,
pub(crate) green: T,
pub(crate) blue: T,
pub(crate) clear: T,
}
impl<T> Rgbc<T> {
pub(crate) fn map<U>(self, func: impl Fn(T) -> U) -> Rgbc<U> {
let Self { red, green, blue, clear } = self;
Rgbc { red: func(red), green: func(green), blue: func(blue), clear: func(clear) }
}
pub(crate) fn map_async<U, F>(
self,
func: impl Fn(T) -> F,
) -> impl Future<Output = Result<Rgbc<U>, Error>>
where
F: Future<Output = Result<U, Error>>,
{
let Self { red, green, blue, clear } = self;
let red = func(red).map(|result| result.context("map red"));
let green = func(green).map(|result| result.context("map green"));
let blue = func(blue).map(|result| result.context("map blue"));
let clear = func(clear).map(|result| result.context("map clear"));
let fut = futures::future::try_join4(red, green, blue, clear);
fut.map_ok(|(red, green, blue, clear)| Rgbc { red, green, blue, clear })
}
pub(crate) fn multi_map<U>(rgbc1: Self, rgbc2: Self, func: impl Fn(T, T) -> U) -> Rgbc<U> {
let Self { red: red1, green: green1, blue: blue1, clear: clear1 } = rgbc1;
let Self { red: red2, green: green2, blue: blue2, clear: clear2 } = rgbc2;
Rgbc {
red: func(red1, red2),
green: func(green1, green2),
blue: func(blue1, blue2),
clear: func(clear1, clear2),
}
}
pub(crate) fn fold<U>(self, acc: U, func: impl Fn(U, T) -> U) -> U {
let Self { red, green, blue, clear } = self;
[red, green, blue, clear].into_iter().fold(acc, func)
}
#[cfg(test)]
pub(crate) fn match_all(left: Self, right: Self, predicate: impl Fn(T, T) -> bool) -> bool {
let Rgbc { red, green, blue, clear } = Self::multi_map(left, right, predicate);
red && green && blue && clear
}
}
impl<T> Sub for Rgbc<T>
where
T: Sub<Output = T> + Copy,
{
type Output = Rgbc<T::Output>;
fn sub(self, rhs: Self) -> Self::Output {
Rgbc::multi_map(self, rhs, |left, right| left - right)
}
}
impl<T> Add for Rgbc<T>
where
T: Add<Output = T> + Copy,
{
type Output = Rgbc<T::Output>;
fn add(self, rhs: Self) -> Self::Output {
Rgbc::multi_map(self, rhs, |left, right| left + right)
}
}
impl<T> Mul for Rgbc<T>
where
T: Mul<Output = T> + Copy,
{
type Output = Rgbc<T::Output>;
fn mul(self, rhs: Self) -> Self::Output {
Rgbc::multi_map(self, rhs, |left, right| left * right)
}
}
impl<T> Div for Rgbc<T>
where
T: Div<Output = T> + Copy,
{
type Output = Rgbc<T::Output>;
fn div(self, rhs: Self) -> Self::Output {
Rgbc::multi_map(self, rhs, |left, right| left / right)
}
}
#[derive(Deserialize, Debug)]
pub struct Configuration {
pub calibration: Option<CalibrationConfiguration>,
pub sensor: SensorConfiguration,
}
#[derive(Deserialize, Debug)]
pub struct CalibrationConfiguration {
pub(crate) leds: Vec<LedConfig>,
pub(crate) off: Rgbc<String>,
pub(crate) all_on: Rgbc<String>,
pub(crate) golden_calibration_params: Rgbc<Parameters>,
}
#[derive(Deserialize, Debug)]
pub struct SensorConfiguration {
pub(crate) vendor_id: u32,
pub(crate) product_id: u32,
pub(crate) rgbc_to_lux_coefficients: Rgbc<f32>,
pub(crate) si_scaling_factors: Rgbc<f32>,
pub(crate) settings: Vec<AdjustmentSetting>,
}
#[derive(Deserialize, Debug)]
pub struct LedConfig {
name: String,
rgbc: Rgbc<String>,
}
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub struct Parameters {
pub(crate) slope: f32,
pub(crate) intercept: f32,
}
type LedMap = HashMap<String, Rgbc<Parameters>>;
#[async_trait(?Send)]
pub trait FileLoader {
async fn load_file(&self, file_path: &str) -> Result<String, Error>;
}
#[derive(Clone, Debug, Serialize)]
pub struct Calibration {
leds: LedMap,
off: Rgbc<Parameters>,
all_on: Rgbc<Parameters>,
calibrated_slope: Rgbc<f32>,
}
impl Calibration {
pub async fn new(
configuration: CalibrationConfiguration,
file_loader: &impl FileLoader,
) -> Result<Self, Error> {
let mut leds = HashMap::new();
for led_config in configuration.leds {
let name = led_config.name;
let config = match led_config
.rgbc
.map_async(
|file_path| async move { Self::parse_file(&file_path, file_loader).await },
)
.await
{
Ok(config) => config,
Err(e) => {
tracing::error!("Failed to map {name:?}'s rgbc field: {e:?}");
tracing::error!("Will not account for {name:?} in calibration");
continue;
}
};
let _ = leds.insert(name.clone(), config);
}
let off = configuration
.off
.map_async(|file_path| async move { Self::parse_file(&file_path, file_loader).await })
.await
.context("Failed to map off rgbc")?;
let all_on = configuration
.all_on
.map_async(|file_path| async move { Self::parse_file(&file_path, file_loader).await })
.await
.context("Failed to map all_on rgbc")?;
let calibrated_slope =
configuration.golden_calibration_params.map(|c| c.slope) / off.map(|c| c.slope);
Ok(Self { leds, off, all_on, calibrated_slope })
}
#[cfg(test)]
pub(crate) fn new_for_test(
leds: LedMap,
off: Rgbc<Parameters>,
all_on: Rgbc<Parameters>,
calibrated_slope: Rgbc<f32>,
) -> Self {
Self { leds, off, all_on, calibrated_slope }
}
async fn parse_file(path: &str, file_loader: &impl FileLoader) -> Result<Parameters, Error> {
let cal_contents = file_loader
.load_file(path)
.await
.with_context(|| format_err!("Could not load {path:?} for parsing"))?;
let mut words = cal_contents.trim().split_ascii_whitespace().skip(2);
let slope: f32 = words
.next()
.ok_or_else(|| format_err!("Missing slope"))?
.parse()
.context("Failed to parse slope")?;
if !slope.is_finite() {
bail!("Slope must not be NaN or Infinity");
}
let intercept: f32 = words
.next()
.ok_or_else(|| format_err!("Missing intercept"))?
.parse()
.context("Failed to parse intercept")?;
if !intercept.is_finite() {
bail!("Intercept must not be NaN or Infinity");
}
Ok(Parameters { slope, intercept })
}
pub(crate) fn leds(&self) -> &LedMap {
&self.leds
}
pub(crate) fn off(&self) -> Rgbc<Parameters> {
self.off
}
pub(crate) fn all_on(&self) -> Rgbc<Parameters> {
self.all_on
}
pub(crate) fn calibrated_slope(&self) -> Rgbc<f32> {
self.calibrated_slope
}
}
#[derive(Copy, Clone, Deserialize, Debug)]
pub(crate) struct AdjustmentSetting {
pub(crate) atime: u32,
pub(crate) gain: u32,
}
#[cfg(test)]
mod types_tests;