monitoring kit
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
#![feature(
    import_trait_associated_functions,
    string_deref_patterns,
    iter_array_chunks,
    portable_simd
)]
use anyhow::*;
use grapher::{truncwrite, Grapher};
use std::array;
use std::convert::identity;
use std::fs::read_to_string as read;
use std::io::Write;
use std::io::{stdout, Read};
use std::path::PathBuf;
use std::result::Result::Ok as Rok;
use std::sync::OnceLock;
use std::thread::sleep;
use std::time::Duration;
use termion::color::*;
use termion::cursor::Hide;
use termion::raw::IntoRawMode;
use termion::screen::IntoAlternateScreen;
use termion::{async_stdin, clear, cursor, style};

fn fetch<U, E: Into<anyhow::Error>>(find: &str, f: impl FnOnce(&str) -> Result<U, E>) -> Result<U> {
    f(read(path.get().unwrap().join("uevent"))?
        .lines()
        .filter_map(|x| x.split_once('='))
        .find(|x| x.0 == find)
        .ok_or(anyhow!("no power??"))?
        .1)
    .map_err(Into::into)
}

static path: OnceLock<PathBuf> = OnceLock::new();
fn main() -> Result<()> {
    let x = std::fs::read_dir("/sys/class/power_supply")?
        .filter_map(Result::ok)
        .find(|x| matches!(read(x.path().join("type")), Rok(x) if x.trim_ascii() == "Battery"))
        .ok_or(anyhow!("batless"))?
        .path();
    path.set(x.clone()).unwrap();
    let manufacturer = fetch("POWER_SUPPLY_MANUFACTURER", |x| Ok(x.to_string()))?;
    let model = fetch("POWER_SUPPLY_MODEL_NAME", |x| Ok(x.to_string()))?;
    let cap = fetch("POWER_SUPPLY_ENERGY_FULL_DESIGN", str::parse::<f64>)? / 1e6;

    let charge = match &*std::env::args().nth(1).unwrap_or("charge".into()) {
        x @ ("charge" | "usage") => x == "charge",
        _ => bail!("arg must be {{usage, charge}}"),
    };

    let mut g = Grapher::new()?;
    let mut d = 1;

    let mut stdout = stdout().into_raw_mode()?.into_alternate_screen()?;
    let mut stdin = async_stdin();
    write!(stdout, "{}{}{}", Hide, clear::All, style::Reset).unwrap();
    let mut second = 0;
    'out: loop {
        let mut key = 0;
        while stdin.read(array::from_mut(&mut key)).unwrap() != 0 {
            match key {
                b'q' => break 'out,
                b'+' => d = (d + 1).min(100),
                b'-' if d > 1 => d -= 1,
                b'-' => d = 1,
                _ => (),
            }
        }

        g.draw(|_| None, identity)?;
        let current = fetch("POWER_SUPPLY_CAPACITY", str::parse::<f64>)?;
        let whs = fetch("POWER_SUPPLY_ENERGY_NOW", str::parse::<f64>)? / 1e6;
        let usage = fetch("POWER_SUPPLY_POWER_NOW", str::parse::<f64>)? / 1e6;

        write!(g.buffer, "{}{}", White.fg_str(), cursor::Goto(1, 1))?;
        truncwrite!(
            g.buffer,
            " {d}s/f ──── {usage:.2}W → ── {current}% {whs:.1}Wh / {cap:.0}Wh ── {manufacturer} {model}"
        )?;
        write!(stdout, "{}", Rgb(40, 185, 119).fg_string())?;
        stdout.write_all(&g.buffer)?;
        stdout.flush()?;
        if second % (d * 2) == 0 {
            g.push_point(if charge { current / 1e2 } else { usage / 40. });
        }

        sleep(Duration::from_millis(500));
        second += 1;
    }
    println!("\x1B[?7l");
    Ok(())
}