Skip to main content

battery_estimator/
error.rs

1//! Error types for battery SOC estimation
2//!
3//! This module defines the error types that can occur during battery
4//! state-of-charge estimation operations.
5
6use thiserror::Error;
7
8/// Errors that can occur during battery SOC estimation
9///
10/// This enum represents all possible error conditions that may arise
11/// when using the battery estimator library.
12///
13/// # Examples
14///
15/// ```no_run
16/// use battery_estimator::{BatteryChemistry, SocEstimator, Error};
17///
18/// let estimator = SocEstimator::new(BatteryChemistry::LiPo);
19///
20/// match estimator.estimate_soc(3.7) {
21///     Ok(soc) => println!("SOC: {:.1}%", soc),
22///     Err(Error::InvalidCurve) => eprintln!("Invalid battery curve"),
23///     Err(Error::NumericalError) => eprintln!("Calculation error"),
24///     Err(Error::InvalidTemperature) => eprintln!("Invalid temperature"),
25/// }
26/// ```
27#[derive(Error, Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum Error {
29    /// The voltage curve data is invalid
30    ///
31    /// This error occurs when:
32    /// - The curve has fewer than 2 points (cannot interpolate)
33    /// - The curve points are not properly ordered
34    /// - The curve has duplicate voltage values
35    ///
36    /// # Examples
37    ///
38    /// ```no_run
39    /// use battery_estimator::{Curve, CurvePoint, Error};
40    ///
41    /// // Invalid: Only one point
42    /// let invalid_curve = Curve::new(&[CurvePoint::new(3.7, 50.0)]);
43    /// let result = invalid_curve.voltage_to_soc(3.7);
44    /// assert!(matches!(result, Err(Error::InvalidCurve)));
45    /// ```
46    #[error("Invalid voltage curve")]
47    InvalidCurve,
48
49    /// A numerical error occurred during calculation
50    ///
51    /// This error occurs when:
52    /// - Division by zero in interpolation
53    /// - Overflow or underflow in calculations
54    /// - Invalid floating-point operations
55    ///
56    /// # Examples
57    ///
58    /// ```no_run
59    /// use battery_estimator::{Curve, CurvePoint, Error};
60    ///
61    /// // This could occur if curve points have the same voltage
62    /// let problematic_curve = Curve::new(&[
63    ///     CurvePoint::new(3.7, 50.0),
64    ///     CurvePoint::new(3.7, 60.0), // Duplicate voltage!
65    /// ]);
66    /// ```
67    #[error("Numerical error in calculation")]
68    NumericalError,
69
70    /// The temperature value is invalid
71    ///
72    /// This error occurs when:
73    /// - Temperature is NaN (Not a Number)
74    /// - Temperature is infinity
75    /// - Temperature is outside reasonable bounds
76    ///
77    /// # Examples
78    ///
79    /// ```no_run
80    /// use battery_estimator::{BatteryChemistry, SocEstimator, Error};
81    ///
82    /// let estimator = SocEstimator::new(BatteryChemistry::LiPo);
83    ///
84    /// // Invalid temperature
85    /// let result = estimator.estimate_soc_with_temp(3.7, f32::NAN);
86    /// ```
87    #[error("Invalid temperature")]
88    InvalidTemperature,
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use core::fmt::Write;
95
96    #[test]
97    fn test_error_display() {
98        // In no-std, Display is available via core::fmt
99        // We can test that the Display implementation compiles and works
100        let mut buffer = [0u8; 64];
101
102        // Test InvalidCurve
103        let mut writer = BufferWriter::new(&mut buffer);
104        write!(writer, "{}", Error::InvalidCurve).unwrap();
105        assert_eq!(writer.as_str(), "Invalid voltage curve");
106
107        // Test NumericalError
108        let mut writer = BufferWriter::new(&mut buffer);
109        write!(writer, "{}", Error::NumericalError).unwrap();
110        assert_eq!(writer.as_str(), "Numerical error in calculation");
111
112        // Test InvalidTemperature
113        let mut writer = BufferWriter::new(&mut buffer);
114        write!(writer, "{}", Error::InvalidTemperature).unwrap();
115        assert_eq!(writer.as_str(), "Invalid temperature");
116    }
117
118    #[test]
119    fn test_error_equality() {
120        assert_eq!(Error::InvalidCurve, Error::InvalidCurve);
121        assert_eq!(Error::NumericalError, Error::NumericalError);
122        assert_eq!(Error::InvalidTemperature, Error::InvalidTemperature);
123
124        assert_ne!(Error::InvalidCurve, Error::NumericalError);
125    }
126
127    #[test]
128    fn test_error_copy() {
129        let error1 = Error::InvalidCurve;
130        let error2 = error1;
131        assert_eq!(error1, error2);
132    }
133
134    #[test]
135    fn test_error_debug() {
136        let error = Error::NumericalError;
137        let mut buffer = [0u8; 64];
138        let mut writer = BufferWriter::new(&mut buffer);
139        write!(writer, "{:?}", error).unwrap();
140        assert!(writer.as_str().contains("NumericalError"));
141    }
142
143    #[test]
144    fn test_error_all_variants() {
145        // Test that all error variants can be created
146        let errors = [
147            Error::InvalidCurve,
148            Error::NumericalError,
149            Error::InvalidTemperature,
150        ];
151        assert_eq!(errors.len(), 3);
152    }
153
154    #[test]
155    fn test_error_variants_distinct() {
156        let error1 = Error::InvalidCurve;
157        let error2 = Error::NumericalError;
158        let error3 = Error::InvalidTemperature;
159
160        // Verify all variants are distinct
161        assert_ne!(error1, error2);
162        assert_ne!(error2, error3);
163        assert_ne!(error1, error3);
164    }
165
166    // Helper struct for testing Display in no-std
167    struct BufferWriter<'a> {
168        buffer: &'a mut [u8],
169        pos: usize,
170    }
171
172    impl<'a> BufferWriter<'a> {
173        fn new(buffer: &'a mut [u8]) -> Self {
174            BufferWriter { buffer, pos: 0 }
175        }
176
177        fn as_str(&self) -> &str {
178            core::str::from_utf8(&self.buffer[..self.pos]).unwrap()
179        }
180    }
181
182    impl<'a> core::fmt::Write for BufferWriter<'a> {
183        fn write_str(&mut self, s: &str) -> core::fmt::Result {
184            let bytes = s.as_bytes();
185            if self.pos + bytes.len() > self.buffer.len() {
186                return Err(core::fmt::Error);
187            }
188            self.buffer[self.pos..self.pos + bytes.len()].copy_from_slice(bytes);
189            self.pos += bytes.len();
190            Ok(())
191        }
192    }
193}