1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
use std::error::Error; use std::fmt; use std::fs; use std::io::prelude::*; #[derive(Clone, Copy)] pub enum ChargingState { Charging, Discharging, Full, } pub struct PowerSupplyInfo { pub name: String, pub remaining_capacity: Option<u32>, pub remaining_energy: Option<u32>, pub present_rate: Option<u32>, pub voltage: Option<u32>, pub design_capacity: Option<u32>, pub design_capacity_unit: Option<u32>, pub last_capacity: Option<u32>, pub last_capacity_unit: Option<u32>, pub hours: u32, pub minutes: u32, pub seconds: u32, pub percentage: Option<u32>, pub is_battery: bool, pub state: Option<ChargingState>, } pub fn get_power_supply_info() -> Result<Vec<PowerSupplyInfo>, Box<dyn Error>> { let mut results: Vec<PowerSupplyInfo> = vec![]; let power_supply_path = "/sys/class/power_supply"; for entry in fs::read_dir(&power_supply_path)? { results.push(PowerSupplyInfo::new(&entry?)?); } Ok(results) } impl PowerSupplyInfo { pub fn new(entry: &fs::DirEntry) -> Result<PowerSupplyInfo, Box<dyn Error>> { let name = entry.file_name().into_string().unwrap(); let voltage = parse_file_to_u32(entry, "voltage_now", 1000)?; let remaining_capacity = parse_file_to_u32(entry, "charge_now", 1000)?; let remaining_energy = parse_file_to_u32(entry, "energy_now", 1000)?; let present_rate = parse_file_to_u32(entry, "current_now", 1000)?; let design_capacity = parse_file_to_u32(entry, "charge_full_design", 1000)?; let design_capacity_unit = parse_file_to_u32(entry, "energy_full_design", 1000)?; let last_capacity = parse_file_to_u32(entry, "charge_full", 1000)?; let last_capacity_unit = parse_file_to_u32(entry, "energy_full", 1000)?; let is_battery = match parse_entry_file(entry, "type")? { Some(val) => val.to_lowercase() == "battery", None => false, }; let state = match parse_entry_file(entry, "status")? { Some(val) => { if val.trim().to_lowercase() == "charging" { Some(ChargingState::Charging) } else if val.trim().to_lowercase() == "discharging" { Some(ChargingState::Discharging) } else if val.trim().to_lowercase() == "full" { Some(ChargingState::Full) } else { None } } None => None, }; let percentage = if remaining_capacity.is_some() && last_capacity.is_some() { Some(remaining_capacity.unwrap() * 100 / last_capacity.unwrap()) } else { None }; let mut seconds = if remaining_capacity.is_some() && present_rate.is_some() { match state.unwrap() { ChargingState::Discharging => { 3600 * remaining_capacity.unwrap() / (present_rate.unwrap() + 1) } ChargingState::Charging => { 3600 * (last_capacity.unwrap() - remaining_capacity.unwrap()) / present_rate.unwrap() } _ => 0, } } else { 0 }; let hours = seconds / 3600; seconds = seconds - (3600 * hours); let minutes = seconds / 60; seconds = seconds - (60 * minutes); Ok(PowerSupplyInfo { name, remaining_capacity, remaining_energy, present_rate, voltage, design_capacity, design_capacity_unit, last_capacity, last_capacity_unit, hours, minutes, seconds, percentage, is_battery, state, }) } } impl fmt::Display for PowerSupplyInfo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_battery { let state = match &self.state { Some(val) => match val { ChargingState::Charging => "Charging", ChargingState::Discharging => "Discharging", ChargingState::Full => "Full", }, None => "", }; let not_full_string = format!( ", {:02}:{:02}:{:02}", self.hours, self.minutes, self.seconds ); let charge_time_string = match &self.state.unwrap() { ChargingState::Charging => format!("{} {}", not_full_string, "until charged"), ChargingState::Discharging => format!("{} {}", not_full_string, "remaining"), _ => String::from(""), }; write!( f, "{}: {}, {}%{}", self.name, state, self.percentage.unwrap(), charge_time_string ) } else { write!(f, "{}", self.name) } } } fn parse_entry_file( entry: &fs::DirEntry, file: &'static str, ) -> Result<Option<String>, Box<dyn Error>> { let mut path = entry.path(); path.push(file); let mut result = String::new(); if path.is_file() { if let Some(filename) = path.to_str() { let mut f = fs::File::open(filename)?; f.read_to_string(&mut result)?; let contents = result.trim(); return Ok(Some(String::from(contents))); } } Ok(None) } fn parse_file_to_u32( entry: &fs::DirEntry, file: &'static str, scalar: u32, ) -> Result<Option<u32>, Box<dyn Error>> { let result = match parse_entry_file(entry, file)? { Some(val) => Some(val.parse::<u32>()? / scalar), None => None, }; Ok(result) }