Fanspeeds as floats, 0 is lowest speed, bugfixes

1. The fanspeeds can now be specified as floats for better granularity
2. Removed 4 as the lowest possible speed. Lowest possible value is now 0.0
3. min and max values are now correctly read for pwm1 and not the rpm
   values of fan1 anymore
4. Percentage speed now gets correctly converted to the pwm values
   (usually between 0 and 255)
5. Before the update of the pwm-value was skipped when the current speed
   was below minimum or above maximum. This got removed. Why was that
   there in the first place?
6. Simpler algorithm for speed_for_temp
7. in monitor the min value is now actually below MIN and the max value
   below MAX. Before it was swapped
8. Fixed grammar error "can's" instead of "can't"
This commit is contained in:
Stefan Gehr 2021-07-19 22:25:34 +02:00
parent 0794605006
commit 90e212a158
4 changed files with 79 additions and 103 deletions

2
Cargo.lock generated
View File

@ -1,7 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"

View File

@ -35,35 +35,35 @@ cards = ["card0"]
[[speed_matrix]]
temp = 4.0
speed = 4
speed = 4.0
[[speed_matrix]]
temp = 30.0
speed = 33
speed = 33.0
[[speed_matrix]]
temp = 45.0
speed = 50
speed = 50.0
[[speed_matrix]]
temp = 60.0
speed = 66
speed = 66.0
[[speed_matrix]]
temp = 65.0
speed = 69
speed = 69.0
[[speed_matrix]]
temp = 70.0
speed = 75
speed = 75.0
[[speed_matrix]]
temp = 75.0
speed = 89
speed = 89.0
[[speed_matrix]]
temp = 80.0
speed = 100
speed = 100.0
```
## :bookmark: License

View File

@ -87,7 +87,7 @@ impl Serialize for Card {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MatrixPoint {
pub temp: f64,
pub speed: u32,
pub speed: f64,
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
@ -119,6 +119,12 @@ impl From<LogLevel> for LevelFilter {
}
}
// linear mapping between two points
fn linear_map(x: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
let m = (y2 - y1) / (x2 - x1);
m * (x - x1) + y1
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Config {
log_level: LogLevel,
@ -131,54 +137,29 @@ impl Config {
&self.cards
}
pub fn speed_for_temp(&self, temp: f64) -> u32 {
pub fn speed_for_temp(&self, temp: f64) -> f64 {
let idx = match self.speed_matrix.iter().rposition(|p| p.temp <= temp) {
Some(idx) => idx,
_ => return 4,
_ => return self.min_speed(),
};
match (idx, self.speed_matrix.len() - 1) {
(0, _) => self.min_speed(),
(current, max) if current == max => self.max_speed(),
_ => {
if self.is_exact_point(idx, temp) {
return self.speed_matrix.get(idx).map(|p| p.speed).unwrap_or(4);
}
let max = match self.speed_matrix.get(idx + 1) {
Some(p) => p,
_ => return 4,
};
let min = match self.speed_matrix.get(idx) {
Some(p) => p,
_ => return 4,
};
let speed_diff = max.speed as f64 - min.speed as f64;
let temp_diff = max.temp as f64 - min.temp as f64;
let increase_by =
(((temp as f64 - min.temp as f64) / temp_diff) * speed_diff).round();
min.speed + increase_by as u32
}
if idx == self.speed_matrix.len() - 1 {
return self.max_speed();
}
linear_map(temp, self.speed_matrix[idx].temp, self.speed_matrix[idx].speed, self.speed_matrix[idx+1].temp, self.speed_matrix[idx+1].speed)
}
pub fn log_level(&self) -> LogLevel {
self.log_level
}
fn min_speed(&self) -> u32 {
self.speed_matrix.first().map(|p| p.speed).unwrap_or(4)
fn min_speed(&self) -> f64 {
self.speed_matrix.first().map(|p| p.speed).unwrap_or(0f64)
}
fn max_speed(&self) -> u32 {
self.speed_matrix.last().map(|p| p.speed).unwrap_or(100)
}
fn is_exact_point(&self, idx: usize, temp: f64) -> bool {
static DELTA: f64 = 0.001f64;
self.speed_matrix
.get(idx)
.map(|p| p.temp - DELTA < temp && p.temp + DELTA > temp)
.unwrap_or(false)
fn max_speed(&self) -> f64 {
self.speed_matrix.last().map(|p| p.speed).unwrap_or(100f64)
}
}
@ -190,35 +171,35 @@ impl Default for Config {
speed_matrix: vec![
MatrixPoint {
temp: 4f64,
speed: 4,
speed: 4f64,
},
MatrixPoint {
temp: 30f64,
speed: 33,
speed: 33f64,
},
MatrixPoint {
temp: 45f64,
speed: 50,
speed: 50f64,
},
MatrixPoint {
temp: 60f64,
speed: 66,
speed: 66f64,
},
MatrixPoint {
temp: 65f64,
speed: 69,
speed: 69f64,
},
MatrixPoint {
temp: 70f64,
speed: 75,
speed: 75f64,
},
MatrixPoint {
temp: 75f64,
speed: 89,
speed: 89f64,
},
MatrixPoint {
temp: 80f64,
speed: 100,
speed: 100f64,
},
],
}
@ -239,24 +220,15 @@ pub fn load_config() -> std::io::Result<Config> {
}
};
if config.speed_matrix.iter().fold(
1000,
|n, point| if point.speed < n { point.speed } else { n },
) < 4
{
log::error!("Due to driver bug lowest fan speed must be greater or equal 4");
return Err(std::io::Error::from(ErrorKind::InvalidData));
}
let mut last_point: Option<&MatrixPoint> = None;
for matrix_point in config.speed_matrix.iter() {
if matrix_point.speed <= 0 {
log::error!("Fan speed can's be below 0 found {}", matrix_point.speed);
if matrix_point.speed < 0f64 {
log::error!("Fan speed can't be below 0.0 found {}", matrix_point.speed);
return Err(std::io::Error::from(ErrorKind::InvalidData));
}
if matrix_point.speed > 100 {
log::error!("Fan speed can's be above 100 found {}", matrix_point.speed);
if matrix_point.speed > 100f64 {
log::error!("Fan speed can't be above 100.0 found {}", matrix_point.speed);
return Err(std::io::Error::from(ErrorKind::InvalidData));
}
if let Some(last_point) = last_point {
@ -317,13 +289,13 @@ mod speed_for_temp {
#[test]
fn below_minimal() {
let config = Config::default();
assert_eq!(config.speed_for_temp(1f64), 4);
assert_eq!(config.speed_for_temp(1f64), 4f64);
}
#[test]
fn minimal() {
let config = Config::default();
assert_eq!(config.speed_for_temp(4f64), 4);
assert_eq!(config.speed_for_temp(4f64), 4f64);
}
#[test]
@ -331,7 +303,7 @@ mod speed_for_temp {
let config = Config::default();
// 45 -> 50
// 60 -> 66
assert_eq!(config.speed_for_temp(46f64), 51);
assert_eq!(config.speed_for_temp(46f64).round(), 51f64);
}
#[test]
@ -339,7 +311,7 @@ mod speed_for_temp {
let config = Config::default();
// 45 -> 50
// 60 -> 66
assert_eq!(config.speed_for_temp(58f64), 64);
assert_eq!(config.speed_for_temp(58f64).round(), 64f64);
}
#[test]
@ -347,24 +319,24 @@ mod speed_for_temp {
let config = Config::default();
// 45 -> 50
// 60 -> 66
assert_eq!(config.speed_for_temp(59f64), 65);
assert_eq!(config.speed_for_temp(59f64).round(), 65f64);
}
#[test]
fn average() {
let config = Config::default();
assert_eq!(config.speed_for_temp(60f64), 66);
assert_eq!(config.speed_for_temp(60f64), 66f64);
}
#[test]
fn max() {
let config = Config::default();
assert_eq!(config.speed_for_temp(80f64), 100);
assert_eq!(config.speed_for_temp(80f64), 100f64);
}
#[test]
fn above_max() {
let config = Config::default();
assert_eq!(config.speed_for_temp(160f64), 100);
assert_eq!(config.speed_for_temp(160f64), 100f64);
}
}

View File

@ -3,6 +3,7 @@ mod config;
extern crate log;
use std::io::ErrorKind;
use std::cmp::{min, max};
use crate::config::{load_config, Card, Config};
use gumdrop::Options;
@ -25,8 +26,8 @@ pub enum AmdFanError {
pub struct HwMon {
card: Card,
name: String,
fan_min: Option<u32>,
fan_max: Option<u32>,
pwm_min: Option<u32>,
pwm_max: Option<u32>,
}
impl HwMon {
@ -34,8 +35,8 @@ impl HwMon {
Self {
card: card.clone(),
name: String::from(name),
fan_min: None,
fan_max: None,
pwm_min: None,
pwm_max: None,
}
}
@ -51,33 +52,33 @@ impl HwMon {
self.read("name")
}
pub fn fan_min(&mut self) -> u32 {
if self.fan_min.is_none() {
self.fan_min = Some(
self.read("fan1_min")
pub fn pwm_min(&mut self) -> u32 {
if self.pwm_min.is_none() {
self.pwm_min = Some(
self.read("pwm1_min")
.unwrap_or_default()
.parse()
.unwrap_or(0),
)
};
self.fan_min.unwrap_or(0)
self.pwm_min.unwrap_or(0)
}
pub fn fan_max(&mut self) -> u32 {
if self.fan_max.is_none() {
self.fan_max = Some(
self.read("fan1_max")
pub fn pwm_max(&mut self) -> u32 {
if self.pwm_max.is_none() {
self.pwm_max = Some(
self.read("pwm1_max")
.unwrap_or_default()
.parse()
.unwrap_or(255),
)
};
self.fan_max.unwrap_or(255)
self.pwm_max.unwrap_or(255)
}
pub fn fan_speed(&self) -> std::io::Result<u64> {
self.read("fan1_input")?.parse().map_err(|_e| {
log::warn!("Read from gpu monitor failed. Invalid fan speed");
pub fn pwm(&self) -> std::io::Result<u32> {
self.read("pwm1")?.parse().map_err(|_e| {
log::warn!("Read from gpu monitor failed. Invalid pwm value");
std::io::Error::from(ErrorKind::InvalidInput)
})
}
@ -115,11 +116,19 @@ impl HwMon {
self.write("pwm1_enable", 2)
}
pub fn set_speed(&self, speed: u64) -> std::io::Result<()> {
pub fn set_pwm(&self, value: u32) -> std::io::Result<()> {
if self.is_fan_automatic() {
self.set_manual()?;
}
self.write("pwm1", speed)
self.write("pwm1", value as u64)
}
pub fn set_speed(&mut self, speed: f64) -> std::io::Result<()> {
let mut pwm = (speed / 100f64 * 255f64).round() as u32;
// stay in the range
pwm = max(pwm, self.pwm_min());
pwm = min(pwm, self.pwm_max());
self.set_pwm(pwm)
}
fn read(&self, name: &str) -> std::io::Result<String> {
@ -289,11 +298,8 @@ fn service(config: Config) -> std::io::Result<()> {
let gpu_temp = controller.hw_mon.gpu_temp().unwrap_or_default();
let speed = config.speed_for_temp(gpu_temp);
if controller.hw_mon.fan_min() > speed || controller.hw_mon.fan_max() < speed {
continue;
}
if let Err(e) = controller.hw_mon.set_speed(speed as u64) {
if let Err(e) = controller.hw_mon.set_speed(speed) {
log::error!("Failed to change speed to {}. {:?}", speed, e);
}
controller.last_temp = gpu_temp;
@ -342,15 +348,15 @@ fn monitor_cards(config: Config) -> std::io::Result<()> {
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
for card in controllers.iter_mut() {
println!(
"Card {:3} | Temp | fan speed | MAX | MIN ",
"Card {:3} | Temp | current PWM | MIN | MAX ",
card.hw_mon.card.0
);
println!(
" | {:>5.2} | {:>9} | {:>4} | {:>4}",
" | {:>5.2} | {:>11} | {:>4} | {:>4}",
card.hw_mon.gpu_temp().unwrap_or_default(),
card.hw_mon.fan_speed().unwrap_or_default(),
card.hw_mon.fan_min(),
card.hw_mon.fan_max(),
card.hw_mon.pwm().unwrap_or_default(),
card.hw_mon.pwm_min(),
card.hw_mon.pwm_max(),
);
}
std::thread::sleep(std::time::Duration::from_secs(4));