Skip to content

Commit 0a0e92e

Browse files
committed
Add portable battery type
1 parent ae57592 commit 0a0e92e

File tree

3 files changed

+228
-2
lines changed

3 files changed

+228
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ sudo dmitui
5151
- [x] Chassis (type 3) (Partially)
5252
- [x] Firmware Language Information (type 13)
5353
- [x] Physical Memory Array (type 16)
54+
- [x] Portable Battery (type 22)
5455

5556
## ⚖️ License
5657

src/dmi.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod baseboard;
2+
mod battery;
23
mod chassis;
34
mod firmware;
45
mod memory;
@@ -13,6 +14,7 @@ use std::{
1314
use anyhow::{Result, bail};
1415

1516
use crate::dmi::baseboard::Baseboard;
17+
use crate::dmi::battery::Battery;
1618
use crate::dmi::chassis::Chassis;
1719
use crate::dmi::firmware::Firmware;
1820
use crate::dmi::memory::{Memory, PhysicalMemoryArray};
@@ -34,6 +36,7 @@ pub struct DMI {
3436
baseboard: Baseboard,
3537
chassis: Chassis,
3638
memory: Memory,
39+
battery: Battery,
3740
pub focused_section: FocusedSection,
3841
}
3942

@@ -45,6 +48,7 @@ pub enum FocusedSection {
4548
Baseboard,
4649
Chassis,
4750
Memory,
51+
Battery,
4852
}
4953

5054
#[derive(Debug)]
@@ -63,6 +67,7 @@ impl From<[u8; 4]> for Header {
6367
3 => StructureType::Chassis,
6468
13 => StructureType::FirmwareLanguage,
6569
16 => StructureType::PhysicalMemoryArray,
70+
22 => StructureType::Battery,
6671
127 => StructureType::End,
6772
_ => StructureType::Other,
6873
};
@@ -84,6 +89,7 @@ pub enum StructureType {
8489
Chassis = 3,
8590
FirmwareLanguage = 13,
8691
PhysicalMemoryArray = 16,
92+
Battery = 22,
8793
End = 127,
8894
Other = 255,
8995
}
@@ -97,6 +103,7 @@ impl DMI {
97103
let mut baseboard: Option<Baseboard> = None;
98104
let mut chassis: Option<Chassis> = None;
99105
let mut memory: Option<Memory> = None;
106+
let mut battery: Option<Battery> = None;
100107

101108
let dmi_file_path = Path::new("/sys/firmware/dmi/tables/DMI");
102109

@@ -179,6 +186,9 @@ impl DMI {
179186
physical_memory_array: PhysicalMemoryArray::from(data.as_slice()),
180187
});
181188
}
189+
StructureType::Battery => {
190+
battery = Some(Battery::from((data, text)));
191+
}
182192
_ => {}
183193
}
184194
}
@@ -189,6 +199,7 @@ impl DMI {
189199
baseboard: baseboard.unwrap(),
190200
chassis: chassis.unwrap(),
191201
memory: memory.unwrap(),
202+
battery: battery.unwrap(),
192203
focused_section: FocusedSection::Firmware,
193204
})
194205
}
@@ -200,14 +211,16 @@ impl DMI {
200211
FocusedSection::System => self.focused_section = FocusedSection::Baseboard,
201212
FocusedSection::Baseboard => self.focused_section = FocusedSection::Chassis,
202213
FocusedSection::Chassis => self.focused_section = FocusedSection::Memory,
203-
FocusedSection::Memory => self.focused_section = FocusedSection::Firmware,
214+
FocusedSection::Memory => self.focused_section = FocusedSection::Battery,
215+
FocusedSection::Battery => self.focused_section = FocusedSection::Firmware,
204216
},
205217
KeyCode::BackTab => match self.focused_section {
206-
FocusedSection::Firmware => self.focused_section = FocusedSection::Memory,
218+
FocusedSection::Firmware => self.focused_section = FocusedSection::Battery,
207219
FocusedSection::System => self.focused_section = FocusedSection::Firmware,
208220
FocusedSection::Baseboard => self.focused_section = FocusedSection::System,
209221
FocusedSection::Chassis => self.focused_section = FocusedSection::Baseboard,
210222
FocusedSection::Memory => self.focused_section = FocusedSection::Chassis,
223+
FocusedSection::Battery => self.focused_section = FocusedSection::Memory,
211224
},
212225
_ => {}
213226
}
@@ -266,6 +279,16 @@ impl DMI {
266279
Span::from(" Memory ").fg(Color::DarkGray)
267280
}
268281
}
282+
FocusedSection::Battery => {
283+
if is_focused {
284+
Span::styled(
285+
" Battery ",
286+
Style::default().bg(Color::Yellow).fg(Color::Black).bold(),
287+
)
288+
} else {
289+
Span::from(" Battery ").fg(Color::DarkGray)
290+
}
291+
}
269292
}
270293
}
271294

@@ -288,6 +311,7 @@ impl DMI {
288311
self.title_span(FocusedSection::Baseboard),
289312
self.title_span(FocusedSection::Chassis),
290313
self.title_span(FocusedSection::Memory),
314+
self.title_span(FocusedSection::Battery),
291315
]))
292316
.title_alignment(Alignment::Left)
293317
.padding(Padding::top(1))
@@ -319,6 +343,9 @@ impl DMI {
319343
FocusedSection::Memory => {
320344
self.memory.render(frame, section_block);
321345
}
346+
FocusedSection::Battery => {
347+
self.battery.render(frame, section_block);
348+
}
322349
}
323350
}
324351
}

src/dmi/battery.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
use ratatui::{
2+
Frame,
3+
layout::{Constraint, Margin, Rect},
4+
style::Stylize,
5+
widgets::{Block, Cell, Padding, Row, Table},
6+
};
7+
8+
#[derive(Debug)]
9+
pub struct Battery {
10+
location: String,
11+
manufacturer: String,
12+
manufacture_date: String,
13+
serial_number: String,
14+
device_name: String,
15+
device_chemistry: String,
16+
design_capacity: Option<u16>,
17+
design_voltage: Option<u16>,
18+
sbds_version: String,
19+
max_error_in_battery: Option<u8>,
20+
oem_specific: u32,
21+
}
22+
23+
impl From<(Vec<u8>, Vec<String>)> for Battery {
24+
fn from((data, text): (Vec<u8>, Vec<String>)) -> Self {
25+
let manufacture_date = if data[2] == 0 {
26+
let value = u16::from_le_bytes(data[14..16].try_into().unwrap());
27+
let day = (value & 0b11111) as u8;
28+
let month = ((value >> 5) & 0b1111) as u8;
29+
let year = (value >> 9) + 1980;
30+
31+
format!("{}-{:02}-{:02}", year, month, day)
32+
} else {
33+
text[data[2].saturating_sub(1) as usize].clone()
34+
};
35+
36+
let serial_number = if data[3] == 0 {
37+
format!(
38+
"0x{:X}",
39+
u16::from_le_bytes(data[12..14].try_into().unwrap())
40+
)
41+
} else {
42+
text[data[3].saturating_sub(1) as usize].clone()
43+
};
44+
45+
let design_voltage = {
46+
let value = u16::from_le_bytes(data[8..10].try_into().unwrap());
47+
if value == 0 { None } else { Some(value) }
48+
};
49+
50+
let device_chemistry = {
51+
if data[5] == 2 {
52+
text[data[16].saturating_sub(1) as usize].clone()
53+
} else {
54+
Chemistry::from(data[5]).to_string()
55+
}
56+
};
57+
58+
let design_capacity = {
59+
let value = u16::from_le_bytes(data[6..8].try_into().unwrap());
60+
if value == 0 {
61+
None
62+
} else {
63+
Some(value * (data[17] as u16))
64+
}
65+
};
66+
67+
let max_error_in_battery = {
68+
if data[11] == 0xFF {
69+
None
70+
} else {
71+
Some(data[11])
72+
}
73+
};
74+
75+
Self {
76+
location: text[data[0].saturating_sub(1) as usize].clone(),
77+
manufacturer: text[data[1].saturating_sub(1) as usize].clone(),
78+
manufacture_date,
79+
serial_number,
80+
device_name: text[data[4].saturating_sub(1) as usize].clone(),
81+
device_chemistry,
82+
design_capacity,
83+
design_voltage,
84+
sbds_version: text[data[10].saturating_sub(1) as usize].clone(),
85+
max_error_in_battery,
86+
oem_specific: u32::from_le_bytes(data[18..22].try_into().unwrap()),
87+
}
88+
}
89+
}
90+
91+
impl Battery {
92+
pub fn render(&self, frame: &mut Frame, block: Rect) {
93+
let rows = vec![
94+
Row::new(vec![
95+
Cell::from("location").bold(),
96+
Cell::from(self.location.clone()),
97+
]),
98+
Row::new(vec![
99+
Cell::from("Manufacturer").bold(),
100+
Cell::from(self.manufacturer.clone()),
101+
]),
102+
Row::new(vec![
103+
Cell::from("Manufacture Date").bold(),
104+
Cell::from(self.manufacture_date.clone()),
105+
]),
106+
Row::new(vec![
107+
Cell::from("Serial number").bold(),
108+
Cell::from(self.serial_number.clone()),
109+
]),
110+
Row::new(vec![
111+
Cell::from("Name").bold(),
112+
Cell::from(self.device_name.clone()),
113+
]),
114+
Row::new(vec![
115+
Cell::from("Chemistry").bold(),
116+
Cell::from(self.device_chemistry.clone()),
117+
]),
118+
Row::new(vec![
119+
Cell::from("Design Voltage").bold(),
120+
Cell::from({
121+
if let Some(v) = self.design_voltage {
122+
format!("{v} mWV")
123+
} else {
124+
"Unknown".to_string()
125+
}
126+
}),
127+
]),
128+
Row::new(vec![
129+
Cell::from("Design Capacity").bold(),
130+
Cell::from({
131+
if let Some(v) = self.design_capacity {
132+
format!("{v} mWh")
133+
} else {
134+
"Unknown".to_string()
135+
}
136+
}),
137+
]),
138+
Row::new(vec![
139+
Cell::from("SBDS Version").bold(),
140+
Cell::from(self.sbds_version.clone()),
141+
]),
142+
Row::new(vec![
143+
Cell::from("Maximum Error").bold(),
144+
Cell::from({
145+
if let Some(v) = self.max_error_in_battery {
146+
format!("{v}%")
147+
} else {
148+
"Unknown".to_string()
149+
}
150+
}),
151+
]),
152+
Row::new(vec![
153+
Cell::from("OEM specific").bold(),
154+
Cell::from(format!("0x{:X}", self.oem_specific)),
155+
]),
156+
];
157+
158+
let widths = [Constraint::Length(20), Constraint::Fill(1)];
159+
let table = Table::new(rows, widths).block(Block::new().padding(Padding::uniform(2)));
160+
frame.render_widget(table, block.inner(Margin::new(2, 0)));
161+
}
162+
}
163+
164+
#[derive(Debug, strum::Display)]
165+
enum Chemistry {
166+
#[strum(to_string = "Other")]
167+
Other,
168+
#[strum(to_string = "Unknown")]
169+
Unknown,
170+
#[strum(to_string = "Lead Acid")]
171+
LeadAcid,
172+
#[strum(to_string = "Nickel Cadmium")]
173+
NickelCadmium,
174+
#[strum(to_string = "Nickel metal hydride")]
175+
NickelMetalHydride,
176+
#[strum(to_string = "Lithium-ion")]
177+
LithiumIon,
178+
#[strum(to_string = "Zinc air")]
179+
ZincAir,
180+
#[strum(to_string = "Lithium Polymer")]
181+
LithiumPolymer,
182+
}
183+
184+
impl From<u8> for Chemistry {
185+
fn from(value: u8) -> Self {
186+
match value {
187+
1 => Self::Other,
188+
2 => Self::Unknown,
189+
3 => Self::LeadAcid,
190+
4 => Self::NickelCadmium,
191+
5 => Self::NickelMetalHydride,
192+
6 => Self::LithiumIon,
193+
7 => Self::ZincAir,
194+
8 => Self::LithiumPolymer,
195+
_ => unreachable!(),
196+
}
197+
}
198+
}

0 commit comments

Comments
 (0)