use core::fmt;
use crate::{
InfoType,
MalformedStructureError::{self, InvalidFormattedSectionLength},
RawStructure,
};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct PortableBattery<'a> {
pub handle: u16,
pub location: &'a str,
pub manufacturer: &'a str,
pub manufacture_date: ManufactureDate<'a>,
pub serial_number: SerialNumber<'a>,
pub device_name: &'a str,
pub device_chemistry: DeviceChemistry<'a>,
pub design_capacity: DesignCapacity,
pub design_voltage: u16,
pub sbds_version_number: &'a str,
pub maximum_error_in_battery_data: u8,
pub oem_specific: Option<u32>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ManufactureDate<'a> {
None,
Basic(&'a str),
SmartBatteryDataSpecification { year: u16, month: u8, date: u8 },
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SerialNumber<'a> {
None,
Basic(&'a str),
SmartBatteryDataSpecification(u16),
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DesignCapacity {
Unknown,
Data { value: u16, multiplier: u8 },
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DeviceChemistry<'a> {
Other,
Unknown,
LeadAcid,
NickelCadmium,
NickelMetalHydride,
LithiumIon,
ZincAir,
LithiumPolymer,
Undefined(u8),
SmartBatteryDataSpecification(&'a str),
}
impl<'a> PortableBattery<'a> {
pub(crate) fn try_from(structure: RawStructure<'a>) -> Result<Self, MalformedStructureError> {
let handle = structure.handle;
match (structure.version.major, structure.version.minor) {
(2, 1) if structure.length != 0x10 => Err(InvalidFormattedSectionLength(
InfoType::PortableBattery,
handle,
"",
0x10,
)),
v if v >= (2, 2) && structure.length != 0x1A => Err(InvalidFormattedSectionLength(
InfoType::PortableBattery,
handle,
"",
0x1A,
)),
_ => Ok(Self {
handle,
location: structure.get_string(0x04)?,
manufacturer: structure.get_string(0x05)?,
manufacture_date: ManufactureDate::new(
structure
.get::<u8>(0x06)
.ok()
.filter(|idx| idx != &0)
.and_then(|idx| structure.find_string(idx).ok()),
structure.get::<u16>(0x12).ok(),
),
serial_number: SerialNumber::new(
structure
.get::<u8>(0x07)
.ok()
.filter(|idx| idx != &0)
.and_then(|idx| structure.find_string(idx).ok()),
structure.get::<u16>(0x10).ok(),
),
device_name: structure.get_string(0x08)?,
device_chemistry: DeviceChemistry::new(structure.get::<u8>(0x09)?, structure.get_string(0x14).ok()),
design_capacity: DesignCapacity::new(structure.get::<u16>(0x0A)?, structure.get::<u8>(0x15).ok()),
design_voltage: structure.get::<u16>(0x0C)?,
sbds_version_number: structure.get_string(0x0E)?,
maximum_error_in_battery_data: structure.get::<u8>(0x0F)?,
oem_specific: structure.get::<u32>(0x16).ok(),
}),
}
}
}
impl<'a> ManufactureDate<'a> {
fn new(basic: Option<&'a str>, sbds: Option<u16>) -> Self {
match (basic, sbds) {
(Some(s), _) => Self::Basic(s),
(None, Some(date)) => Self::SmartBatteryDataSpecification {
year: ((date & 0b1111_1110_0000_0000) >> 9) + 1980,
month: ((date & 0b0000_0001_1110_0000) >> 5) as u8,
date: (date & 0b0000_0000_0001_1111) as u8,
},
_ => Self::None,
}
}
}
impl<'a> fmt::Display for ManufactureDate<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => Ok(()),
Self::Basic(s) => write!(f, "{}", s),
Self::SmartBatteryDataSpecification { year, month, date } =>
{
write!(f, "{:04}-{:02}-{:02}", year, month, date)
}
}
}
}
impl<'a> SerialNumber<'a> {
fn new(basic: Option<&'a str>, sbds: Option<u16>) -> Self {
match (basic, sbds) {
(Some(s), _) => Self::Basic(s),
(None, Some(word)) => Self::SmartBatteryDataSpecification(word),
_ => Self::None,
}
}
}
impl<'a> fmt::Display for SerialNumber<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => Ok(()),
Self::Basic(s) => write!(f, "{}", s),
Self::SmartBatteryDataSpecification(word) => write!(f, "{:#04X}", word),
}
}
}
impl<'a> DeviceChemistry<'a> {
fn new(basic: u8, sbds: Option<&'a str>) -> Self {
match basic {
0x01 => Self::Other,
0x02 => {
if let Some(s) = sbds {
Self::SmartBatteryDataSpecification(s)
} else {
Self::Unknown
}
}
0x03 => Self::LeadAcid,
0x04 => Self::NickelCadmium,
0x05 => Self::NickelMetalHydride,
0x06 => Self::LithiumIon,
0x07 => Self::ZincAir,
0x08 => Self::LithiumPolymer,
v => Self::Undefined(v),
}
}
}
impl<'a> fmt::Display for DeviceChemistry<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Other => write!(f, "Other"),
Self::Unknown => write!(f, "Unknown"),
Self::LeadAcid => write!(f, "Lead Acid"),
Self::NickelCadmium => write!(f, "Nickel Cadmium"),
Self::NickelMetalHydride => write!(f, "Nickel metal hydride"),
Self::LithiumIon => write!(f, "Lithium-ion"),
Self::ZincAir => write!(f, "Zinc air"),
Self::LithiumPolymer => write!(f, "Lithium Polymer"),
Self::Undefined(v) => write!(f, "Undefined: {}", v),
Self::SmartBatteryDataSpecification(s) => write!(f, "{}", s),
}
}
}
impl DesignCapacity {
fn new(value: u16, multipler: Option<u8>) -> Self {
if value == 0 {
Self::Unknown
} else {
Self::Data {
value,
multiplier: multipler.unwrap_or(1),
}
}
}
}
impl From<DesignCapacity> for u64 {
fn from(dc: DesignCapacity) -> Self {
match dc {
DesignCapacity::Unknown => 0,
DesignCapacity::Data { value, multiplier } => (value * multiplier as u16).into(),
}
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use std::prelude::v1::*;
#[test]
fn manufacture_date() {
use super::ManufactureDate;
assert_eq!("", format!("{}", ManufactureDate::new(None, None)), "Empty");
assert_eq!(
"07/17/2019",
format!("{}", ManufactureDate::new(Some("07/17/2019"), None)),
"Basic"
);
assert_eq!(
"2000-02-01",
format!("{}", ManufactureDate::new(None, Some(0x2841))),
"SBDS"
);
}
#[test]
fn serial_number() {
use super::SerialNumber;
assert_eq!("", format!("{}", SerialNumber::new(None, None)), "Empty");
assert_eq!(
"S/N 1111",
format!("{}", SerialNumber::new(Some("S/N 1111"), None)),
"Basic"
);
assert_eq!("0xBEAF", format!("{}", SerialNumber::new(None, Some(0xBEAF))), "SBDS");
}
#[test]
fn device_chemistry() {
use super::DeviceChemistry;
let sample = &[
"Undefined: 0",
"Other",
"Unknown",
"Lead Acid",
"Nickel Cadmium",
"Nickel metal hydride",
"Lithium-ion",
"Zinc air",
"Lithium Polymer",
];
for (n, &s) in sample.iter().enumerate() {
let sbds = None;
assert_eq!(s, format!("{}", DeviceChemistry::new(n as u8, sbds)));
if n == 0x02 {
let sbds = Some("PbAc");
assert_eq!("PbAc", format!("{:#}", DeviceChemistry::new(n as u8, sbds)));
}
}
}
#[test]
fn design_capacity() {
use super::DesignCapacity;
assert_eq!(0u64, DesignCapacity::new(0, None).into(), "Unknown");
assert_eq!(0u64, DesignCapacity::new(0, Some(42)).into(), "Unknown");
assert_eq!(4800u64, DesignCapacity::new(4800, None).into(), "w/o multiplier");
assert_eq!(9600u64, DesignCapacity::new(4800, Some(2)).into(), "With multiplier");
}
#[test]
fn portable_battery() {
use super::*;
use crate::{InfoType, RawStructure};
let length = 26;
let (data, strings) =
include_bytes!("../../tests/data/________/entries/22-0/bin")[4..].split_at(length as usize - 4);
let structure = RawStructure {
version: (3, 2).into(),
info: InfoType::PortableBattery,
length,
handle: 0x002B,
data,
strings,
};
let sample = PortableBattery {
handle: 0x002B,
location: "Front",
manufacturer: "LGC",
manufacture_date: ManufactureDate::SmartBatteryDataSpecification {
year: 2020,
month: 7,
date: 1,
},
serial_number: SerialNumber::SmartBatteryDataSpecification(0x058B),
device_name: "5B10W13930",
device_chemistry: DeviceChemistry::SmartBatteryDataSpecification("LiP"),
design_capacity: DesignCapacity::Data {
value: 5100,
multiplier: 10,
},
design_voltage: 15400,
sbds_version_number: "03.01",
maximum_error_in_battery_data: 0xFF,
oem_specific: Some(0),
};
let result = PortableBattery::try_from(structure).unwrap();
assert_eq!(sample, result, "PortableBattery");
}
}