1
use std::{error::Error, ffi::OsString, time::Duration};
2
3
use gpiod::Chip;
4
use i2cdev::{core::I2CDevice, linux::LinuxI2CDevice};
5
6
fn main() -> Result<(), Box<dyn Error>> {
7
if let Some(arg1) = std::env::args_os().skip(1).next() {
8
systemd_shutdown(arg1);
9
return Ok(());
10
}
11
12
let fan_thread = std::thread::spawn(fan_control);
13
let power_button_thread = std::thread::spawn(power_button_monitor);
14
15
fan_thread.join().unwrap()?;
16
power_button_thread.join().unwrap()?;
17
18
Ok(())
19
}
20
21
fn systemd_shutdown(arg1: OsString) {
22
if arg1 == "poweroff" {
23
_ = set_fan_speed(0);
24
_ = prepare_poweroff();
25
} else if arg1 == "reboot" || arg1 == "halt" || arg1 == "kexec" {
26
_ = set_fan_speed(0);
27
} else {
28
panic!("unknown argument {arg1:?}")
29
}
30
}
31
32
const ARGONONE_ADDR: u16 = 0x1a;
33
const ARGONONE_DUTYCYCLE: u8 = 0x80;
34
const ARGONONE_CONTROL: u8 = 0x86;
35
36
const SYS_TEMP: &str = "/sys/class/thermal/thermal_zone0/temp";
37
38
struct TempZone {
39
min_temp: f32,
40
fan_speed: u8,
41
}
42
43
impl TempZone {
44
const fn new(min_temp: f32, fan_speed: u8) -> Self {
45
TempZone {
46
min_temp,
47
fan_speed,
48
}
49
}
50
}
51
52
const TEMP_ZONES: [TempZone; 3] = [
53
TempZone::new(55.0, 30),
54
TempZone::new(60.0, 55),
55
TempZone::new(65.0, 100),
56
];
57
58
const CHECK_INTERVAL: Duration = Duration::from_secs(1);
59
const CHANGE_INTERVAL: Duration = Duration::from_secs(30);
60
61
fn fan_control() -> std::io::Result<()> {
62
set_fan_speed(0)?;
63
let mut current_speed = 0;
64
let mut current_temp = 0.0;
65
66
loop {
67
let temp = get_temp()?;
68
if temp == current_temp {
69
std::thread::sleep(CHECK_INTERVAL);
70
continue;
71
}
72
73
current_temp = temp;
74
75
let speed = TEMP_ZONES
76
.iter()
77
.rfind(|tz| temp >= tz.min_temp)
78
.map_or(0, |tz| tz.fan_speed);
79
80
if speed == current_speed {
81
std::thread::sleep(CHECK_INTERVAL);
82
continue;
83
}
84
85
print!("{temp} Celsius; ");
86
set_fan_speed(speed)?;
87
current_speed = speed;
88
89
std::thread::sleep(CHANGE_INTERVAL);
90
}
91
}
92
93
fn get_temp() -> std::io::Result<f32> {
94
let temp: f32 = std::fs::read_to_string(SYS_TEMP)?
95
.trim_end()
96
.parse()
97
.expect("temp reading should be parseable as number");
98
Ok(temp / 1000.0)
99
}
100
101
fn set_fan_speed(speed: u8) -> std::io::Result<()> {
102
assert!(speed <= 100);
103
println!("setting fan to {speed}%");
104
LinuxI2CDevice::new("/dev/i2c-1", ARGONONE_ADDR)?
105
.smbus_write_byte_data(ARGONONE_DUTYCYCLE, speed)?;
106
Ok(())
107
}
108
109
fn prepare_poweroff() -> std::io::Result<()> {
110
println!("preparing for poweroff");
111
LinuxI2CDevice::new("/dev/i2c-1", ARGONONE_ADDR)?.smbus_write_byte_data(ARGONONE_CONTROL, 1)?;
112
Ok(())
113
}
114
115
const ARGONONE_CHIP: usize = 0;
116
const ARGONONE_SHUTDOWN: u32 = 4;
117
118
fn power_button_monitor() -> std::io::Result<()> {
119
let chip = Chip::new(ARGONONE_CHIP)?;
120
let mut inputs = chip.request_lines(
121
gpiod::Options::input([ARGONONE_SHUTDOWN])
122
.edge(gpiod::EdgeDetect::Both)
123
.consumer("argon"),
124
)?;
125
126
loop {
127
let gpiod::Event {
128
line: _,
129
edge: gpiod::Edge::Rising,
130
time: rose,
131
} = inputs.read_event()?
132
else {
133
continue;
134
};
135
let gpiod::Event {
136
line: _,
137
edge: gpiod::Edge::Falling,
138
time: fell,
139
} = inputs.read_event()?
140
else {
141
continue;
142
};
143
let micros = (fell - rose).as_micros();
144
145
if 19000 <= micros && micros < 22000 {
146
// Power button was double-tapped.
147
systemctl("reboot")?;
148
} else if 39000 <= micros && micros < 42000 {
149
// Power button was held for [3,5) seconds.
150
// AFAICT this already prepares the case to remove power (and it
151
// *will* do so on its own after a bit, I believe), so we must
152
// initiate poweroff before it does it for us.
153
systemctl("poweroff")?;
154
}
155
}
156
}
157
158
fn systemctl(arg: &str) -> std::io::Result<()> {
159
let result = std::process::Command::new("systemctl").arg(arg).output()?;
160
println!("`systemctl {arg}` yielded {}", result.status);
161
println!("stdout:");
162
std::io::Write::write_all(&mut std::io::stdout(), &result.stdout)?;
163
println!("---");
164
println!("stderr:");
165
std::io::Write::write_all(&mut std::io::stdout(), &result.stderr)?;
166
println!("---");
167
Ok(())
168
}
169