cadmus_core/battery/
kobo.rs

1use super::{Battery, Status};
2use crate::device::CURRENT_DEVICE;
3use anyhow::{format_err, Error};
4use std::fs::File;
5use std::io::{Read, Seek, SeekFrom};
6use std::path::Path;
7
8const BATTERY_INTERFACES: [&str; 3] = [
9    "/sys/class/power_supply/bd71827_bat",
10    "/sys/class/power_supply/mc13892_bat",
11    "/sys/class/power_supply/battery",
12];
13const POWER_COVER_INTERFACE: &str = "/sys/class/misc/cilix";
14
15const BATTERY_CAPACITY: &str = "capacity";
16const BATTERY_STATUS: &str = "status";
17
18const POWER_COVER_CAPACITY: &str = "cilix_bat_capacity";
19const POWER_COVER_STATUS: &str = "charge_status";
20const POWER_COVER_CONNECTED: &str = "cilix_conn";
21
22pub struct PowerCover {
23    capacity: File,
24    status: File,
25    connected: File,
26}
27
28// TODO: health, technology, time_to_full_now, time_to_empty_now
29pub struct KoboBattery {
30    capacity: File,
31    status: File,
32    power_cover: Option<PowerCover>,
33}
34
35impl KoboBattery {
36    pub fn new() -> Result<KoboBattery, Error> {
37        let base = Path::new(
38            BATTERY_INTERFACES
39                .iter()
40                .find(|bi| Path::new(bi).exists())
41                .ok_or_else(|| format_err!("battery path missing"))?,
42        );
43        let capacity = File::open(base.join(BATTERY_CAPACITY))?;
44        let status = File::open(base.join(BATTERY_STATUS))?;
45        let power_cover = if CURRENT_DEVICE.has_power_cover() {
46            let base = Path::new(POWER_COVER_INTERFACE);
47            let capacity = File::open(base.join(POWER_COVER_CAPACITY))?;
48            let status = File::open(base.join(POWER_COVER_STATUS))?;
49            let connected = File::open(base.join(POWER_COVER_CONNECTED))?;
50            Some(PowerCover {
51                capacity,
52                status,
53                connected,
54            })
55        } else {
56            None
57        };
58        Ok(KoboBattery {
59            capacity,
60            status,
61            power_cover,
62        })
63    }
64}
65
66impl KoboBattery {
67    fn is_power_cover_connected(&mut self) -> Result<bool, Error> {
68        if let Some(power_cover) = self.power_cover.as_mut() {
69            let mut buf = String::new();
70            power_cover.connected.seek(SeekFrom::Start(0))?;
71            power_cover.connected.read_to_string(&mut buf)?;
72            Ok(buf.trim_end().parse::<u8>().map_or(false, |v| v == 1))
73        } else {
74            Ok(false)
75        }
76    }
77}
78
79impl Battery for KoboBattery {
80    fn capacity(&mut self) -> Result<Vec<f32>, Error> {
81        let mut buf = String::new();
82        self.capacity.seek(SeekFrom::Start(0))?;
83        self.capacity.read_to_string(&mut buf)?;
84        let capacity = buf.trim_end().parse::<f32>().unwrap_or(0.0);
85        if matches!(self.is_power_cover_connected(), Ok(true)) {
86            let mut buf = String::new();
87            self.power_cover.iter_mut().for_each(|power_cover| {
88                power_cover.capacity.seek(SeekFrom::Start(0)).ok();
89                power_cover.capacity.read_to_string(&mut buf).ok();
90            });
91            let aux_capacity = buf.trim_end().parse::<f32>().unwrap_or(0.0);
92            Ok(vec![capacity, aux_capacity])
93        } else {
94            Ok(vec![capacity])
95        }
96    }
97
98    fn status(&mut self) -> Result<Vec<Status>, Error> {
99        let mut buf = String::new();
100        self.status.seek(SeekFrom::Start(0))?;
101        self.status.read_to_string(&mut buf)?;
102        let status = match buf.trim_end() {
103            "Discharging" => Status::Discharging,
104            "Charging" => Status::Charging,
105            "Not charging" | "Full" => Status::Charged,
106            _ => Status::Unknown,
107        };
108        if matches!(self.is_power_cover_connected(), Ok(true)) {
109            let mut buf = String::new();
110            self.power_cover.iter_mut().for_each(|power_cover| {
111                power_cover.status.seek(SeekFrom::Start(0)).ok();
112                power_cover.status.read_to_string(&mut buf).ok();
113            });
114            let aux_status = match buf.trim_end().parse::<i8>() {
115                Ok(0) => Status::Discharging,
116                Ok(2) => Status::Charging,
117                Ok(3) => Status::Charged,
118                _ => Status::Unknown,
119            };
120            Ok(vec![status, aux_status])
121        } else {
122            Ok(vec![status])
123        }
124    }
125}