#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Temperature(i16);
impl Temperature {
pub const fn from_deci(decidegrees: i16) -> Self {
Self(decidegrees)
}
pub fn from_float(degrees: f32) -> Self {
Self((degrees * 10.0).round_ties_even() as i16)
}
pub fn is_setpoint_valid(&self) -> bool {
self.0 >= 100 && self.0 <= 350
}
pub(crate) fn from_setpoint(bits: u8) -> Result<Self, InvalidTemperature> {
if bits < 0xff {
Ok(Self(bits as i16 + 100))
} else {
Err(InvalidTemperature::FromSetpoint(bits))
}
}
pub(crate) fn as_setpoint_bits(&self) -> u8 {
if self.is_setpoint_valid() {
(self.0 - 100) as u8
} else {
Self::invalid().as_setpoint_bits()
}
}
pub(crate) fn from_sensor(bits: u16) -> Result<Self, InvalidTemperature> {
let bits = bits & 0x07ff;
if bits <= 2000 {
Ok(Self(bits as i16 - 500))
} else {
Err(InvalidTemperature::FromSensor(bits))
}
}
pub(crate) fn as_sensor_bits(&self) -> u16 {
let value = self.0 + 500;
if (0..=2000).contains(&value) {
value as u16
} else {
Self::invalid().as_sensor_bits()
}
}
pub(crate) fn invalid() -> InvalidTemperature {
InvalidTemperature::_Constructed
}
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let p = f.precision().unwrap_or(1);
let mut tmp = format!("{:.p$}", self.0.abs() as f32 / 10.0);
if f.alternate() {
tmp.push('\u{2103}');
}
f.pad_integral(self.0 >= 0, "", &tmp)
}
}
impl From<i16> for Temperature {
fn from(value: i16) -> Self {
Self::from_deci(value * 10)
}
}
impl From<f32> for Temperature {
fn from(value: f32) -> Self {
Self::from_float(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub(crate) enum InvalidTemperature {
FromSetpoint(u8),
FromSensor(u16),
#[doc(hidden)]
_Constructed,
}
impl InvalidTemperature {
pub(crate) fn as_setpoint_bits(&self) -> u8 {
0xff
}
pub(crate) fn as_sensor_bits(&self) -> u16 {
0x07ff
}
}
impl std::fmt::Display for InvalidTemperature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FromSetpoint(v) => write!(f, "Invalid setpoint {v:#04x}"),
Self::FromSensor(v) => write!(f, "Invalid sensor value {v:#06x}"),
_ => write!(f, "Invalid temperature"),
}
}
}
impl std::error::Error for InvalidTemperature {}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case(0x96, 250)] #[case(0xA0, 260)] #[case(0x78, 220)] #[case(0x64, 200)] #[case(0x00, 100)]
#[case(0xFA, 350)]
fn test_from_setpoint_ok(#[case] bits: u8, #[case] expected: i16) {
assert_matches!(Temperature::from_setpoint(bits), Ok(t) => {
assert_eq!(t.0, expected);
})
}
#[test]
fn test_from_setpoint_invalid() {
let bits = 0xff;
assert_matches!(Temperature::from_setpoint(bits), Err(InvalidTemperature::FromSetpoint(v)) => {
assert_eq!(v, bits);
});
}
#[rstest]
#[case(0x02E7, 243)] #[case(0x02DA, 230)] #[case(0x02E4, 240)] #[case(0x0000, -500)]
#[case(0x07D0, 1500)]
#[case(0x32E9, 245)] fn test_from_sensor_ok(#[case] bits: u16, #[case] expected: i16) {
assert_matches!(Temperature::from_sensor(bits), Ok(t) => {
assert_eq!(t.0, expected);
})
}
#[rstest]
#[case(0xffff)]
#[case(0x07ff)]
#[case(0x07d1)]
fn test_from_sensor_invalid(#[case] bits: u16) {
assert_matches!(Temperature::from_sensor(bits), Err(InvalidTemperature::FromSensor(v)) => {
assert_eq!(v, bits & 0x07ff);
});
}
#[rstest]
#[case(0)]
#[case(25)]
#[case(500)]
#[case(-1)]
#[case(-100)]
fn test_from_degrees_int(#[case] degrees: i16) {
let t: Temperature = degrees.into();
assert_eq!(t.0, degrees * 10);
}
#[rstest]
#[case(0.0, 0)]
#[case(12.34, 123)]
#[case(12.35, 124)]
#[case(12.45, 124)]
#[case(-1.0, -10)]
fn test_from_degrees_float(#[case] degrees: f32, #[case] expected: i16) {
let t: Temperature = degrees.into();
assert_eq!(t.0, expected);
}
}