Add Asus ROG Ally fan support (without interpolation)
This commit is contained in:
parent
dd0c3998f0
commit
364d9650f2
11 changed files with 191 additions and 117 deletions
2
backend-rs/Cargo.lock
generated
2
backend-rs/Cargo.lock
generated
|
@ -1243,7 +1243,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sysfuss"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
|
|
|
@ -19,7 +19,7 @@ nrpc = { version = "1.0", path = "../../nRPC/nrpc", features = ["async-trait"] }
|
|||
prost = "0.11"
|
||||
tokio = { version = "1", features = ["sync", "rt"] }
|
||||
|
||||
sysfuss = { version = "0.3", features = ["derive"], path = "../../sysfs-nav" }
|
||||
sysfuss = { version = "0.4", features = ["derive"], path = "../../sysfs-nav" }
|
||||
|
||||
# logging
|
||||
log = "0.4"
|
||||
|
|
|
@ -4,4 +4,7 @@ pub use dev_mode::DevModeFan;
|
|||
mod steam_deck;
|
||||
pub use steam_deck::SteamDeckFan;
|
||||
|
||||
mod rog_ally;
|
||||
pub use rog_ally::RogAllyFan;
|
||||
|
||||
pub(self) use super::FanAdapter as Adapter;
|
||||
|
|
159
backend-rs/src/adapters/fans/rog_ally/adapter.rs
Normal file
159
backend-rs/src/adapters/fans/rog_ally/adapter.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use sysfuss::{BasicEntityPath, HwMonAttribute, HwMonAttributeItem, HwMonAttributeType, HwMonPath, SysAttributeExt};
|
||||
use sysfuss::{SysPath, SysEntityAttributesExt};
|
||||
|
||||
use crate::datastructs::{Settings, GraphPoint};
|
||||
|
||||
const ASUS_CUSTOM_FAN_CURVE_HWMON_NAME: &str = "asus_custom_fan_curve";
|
||||
const ASUS_REGULAR_HWMON_NAME: &str = "asus";
|
||||
|
||||
pub struct RogAllyFan {
|
||||
hwmon_curve: HwMonPath,
|
||||
hwmon_asus: HwMonPath,
|
||||
}
|
||||
|
||||
impl RogAllyFan {
|
||||
pub fn maybe_find() -> Option<Self> {
|
||||
let syspath = SysPath::path(crate::sys::SYSFS_ROOT);
|
||||
match syspath.hwmon_by_name(ASUS_CUSTOM_FAN_CURVE_HWMON_NAME)
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs hwmon iter error while finding Asus ROG Ally fan curve: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(hwmon_curve) => {
|
||||
match syspath.hwmon_by_name(ASUS_REGULAR_HWMON_NAME) {
|
||||
Err(e) => {
|
||||
log::error!("sysfs hwmon iter error while finding Asus fan: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(hwmon_asus) => {
|
||||
Some(Self {
|
||||
hwmon_curve,
|
||||
hwmon_asus,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns true when the point probably doesn't exist at all
|
||||
// (instead of it just running out of pwm# entries)
|
||||
fn set_gp(gp_i: u64, gp: &GraphPoint, basic_curve: &BasicEntityPath) -> bool {
|
||||
for pwm_i in 1..u64::MAX {
|
||||
let temp_attr = pwm_point_temp(pwm_i, gp_i);
|
||||
let pwm_attr = pwm_point_pwm(pwm_i, gp_i);
|
||||
if !(temp_attr.exists(basic_curve) && pwm_attr.exists(basic_curve)) {
|
||||
return pwm_i == 1;
|
||||
}
|
||||
let pwm_val = (gp.y * 255.0).round() as u8;
|
||||
if let Err(e) = basic_curve.set(&pwm_attr as &str, pwm_val) {
|
||||
let attr_path = pwm_attr.path(basic_curve);
|
||||
log::error!("failed to write to {}: {}", attr_path.display(), e);
|
||||
}
|
||||
let temp_val = (gp.x * 100.0).round() as u8;
|
||||
if let Err(e) = basic_curve.set(&temp_attr as &str, temp_val) {
|
||||
let attr_path = temp_attr.path(basic_curve);
|
||||
log::error!("failed to write to {}: {}", attr_path.display(), e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::Adapter for RogAllyFan {
|
||||
fn on_enable_toggled(&self, settings: &Settings) {
|
||||
let value_to_set = settings.enable as u8;
|
||||
for i in 1..u64::MAX {
|
||||
let attr = pwm_enable_attr_name(i);
|
||||
if self.hwmon_curve.exists(&attr) {
|
||||
if let Err(e) = self.hwmon_curve.set(attr, value_to_set) {
|
||||
log::error!("failed to write to {}: {}", self.hwmon_curve.path_to(attr).display(), e);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn control_fan(&self, settings: &Settings, _sensor: &crate::adapters::traits::SensorReading) {
|
||||
let basic_curve = BasicEntityPath::new(self.hwmon_curve.as_ref());
|
||||
if settings.curve.is_empty() {
|
||||
for i in 1..u64::MAX {
|
||||
let out_of_points = if i == 1 {
|
||||
Self::set_gp(i as _, &GraphPoint { x: 0.0, y: 0.0 }, &basic_curve)
|
||||
} else {
|
||||
Self::set_gp(i as _, &GraphPoint { x: 1.0, y: 1.0 }, &basic_curve)
|
||||
};
|
||||
if out_of_points { break; }
|
||||
}
|
||||
}
|
||||
for (i, gp) in settings.curve.iter().enumerate() {
|
||||
Self::set_gp(i as _, gp, &basic_curve);
|
||||
}
|
||||
// set remaining pwm points to highest in curve
|
||||
for i in settings.curve.len()..usize::MAX {
|
||||
if Self::set_gp(i as _, &GraphPoint { x: 1.0, y: 1.0 }, &basic_curve) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sensor<'a: 'b, 'b>(&'a self) -> Option<&'b dyn crate::adapters::SensorAdapter> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::super::SensorAdapter for RogAllyFan {
|
||||
fn read(&self) -> Result<crate::adapters::SensorReading, std::io::Error> {
|
||||
let mut readings = Vec::new();
|
||||
let mut last_error = None;
|
||||
for i in 1..u64::MAX {
|
||||
let attr = fan_reading_attr_name(i);
|
||||
let reading = match self.hwmon_asus.attribute::<u64>(attr) {
|
||||
Ok(reading) => reading,
|
||||
Err(sysfuss::EitherErr2::First(e1)) => {
|
||||
log::debug!("failed to read attribute {}: {} (ok; breaking loop...)", self.hwmon_asus.path_to(attr).display(), e1);
|
||||
last_error = Some(e1);
|
||||
break;
|
||||
},
|
||||
Err(sysfuss::EitherErr2::Second(e2)) => {
|
||||
log::debug!("failed to read attribute {}: {} (ok; breaking loop...)", self.hwmon_asus.path_to(attr).display(), e2);
|
||||
last_error = Some(std::io::Error::other(e2));
|
||||
break;
|
||||
},
|
||||
};
|
||||
readings.push(reading);
|
||||
}
|
||||
let mut reading_sum = 0;
|
||||
for reading in readings.iter() {
|
||||
reading_sum += *reading;
|
||||
}
|
||||
if readings.is_empty() {
|
||||
Err(last_error.unwrap())
|
||||
} else {
|
||||
Ok(crate::adapters::SensorReading {
|
||||
value: (reading_sum / (readings.len() as u64)) as f64,
|
||||
meaning: crate::adapters::SensorType::Fan,
|
||||
name: "ROG Ally Fan",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn pwm_enable_attr_name(index: u64) -> HwMonAttribute {
|
||||
HwMonAttribute::new(HwMonAttributeType::Pwm, index, HwMonAttributeItem::Enable)
|
||||
}
|
||||
|
||||
fn fan_reading_attr_name(index: u64) -> HwMonAttribute {
|
||||
HwMonAttribute::new(HwMonAttributeType::Fan, index, HwMonAttributeItem::Input)
|
||||
}
|
||||
|
||||
fn pwm_point_temp(index: u64, point_index: u64) -> String {
|
||||
format!("pwm{}_auto_point{}_temp", index, point_index)
|
||||
}
|
||||
|
||||
fn pwm_point_pwm(index: u64, point_index: u64) -> String {
|
||||
format!("pwm{}_auto_point{}_pwm", index, point_index)
|
||||
}
|
2
backend-rs/src/adapters/fans/rog_ally/mod.rs
Normal file
2
backend-rs/src/adapters/fans/rog_ally/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod adapter;
|
||||
pub use adapter::RogAllyFan;
|
|
@ -174,6 +174,10 @@ fn read_fan(hwmon: &HwMonPath) -> std::io::Result<u64> {
|
|||
}
|
||||
}
|
||||
|
||||
fn fan_range() -> std::ops::Range<f64> {
|
||||
1.0..7000.0
|
||||
}
|
||||
|
||||
fn do_fan_control(settings: &Settings, hwmon: &HwMonPath, thermal_zone: f64) {
|
||||
/*
|
||||
curve = self.settings["curve"]
|
||||
|
@ -193,8 +197,8 @@ fn do_fan_control(settings: &Settings, hwmon: &HwMonPath, thermal_zone: f64) {
|
|||
fan_ratio = self.step_fan(self, index, temperature_ratio)
|
||||
set_fan_target(int((fan_ratio * FAN_MAXIMUM) + FAN_MINIMUM))
|
||||
*/
|
||||
let temperature_ratio = (((thermal_zone as f64)/1000.0) - settings.temperature_bounds.min)
|
||||
/ (settings.temperature_bounds.max - settings.temperature_bounds.min);
|
||||
let fan_bounds = fan_range();
|
||||
let temperature_ratio = ((thermal_zone as f64)/1000.0) / 100.0 /* temperature range in C */;
|
||||
let mut index = None;
|
||||
for i in (0..settings.curve.len()).rev() {
|
||||
if settings.curve[i].x < temperature_ratio {
|
||||
|
@ -207,7 +211,7 @@ fn do_fan_control(settings: &Settings, hwmon: &HwMonPath, thermal_zone: f64) {
|
|||
} else {
|
||||
step_fan(settings, index, temperature_ratio)
|
||||
};
|
||||
let fan_speed: u64 = ((fan_ratio * (settings.fan_bounds.max - settings.fan_bounds.min)) + settings.fan_bounds.min) as _;
|
||||
let fan_speed: u64 = ((fan_ratio * (fan_bounds.end - fan_bounds.start)) + fan_bounds.start) as _;
|
||||
if let Err(e) = write_fan_target(hwmon, fan_speed) {
|
||||
log::error!("Failed to write to Steam Deck fan target file: {}", e);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,14 @@ impl ThermalZoneSensor {
|
|||
pub fn new<P: AsRef<std::path::Path>>(zone_path: P) -> Self {
|
||||
Self { zone: BasicEntityPath::new(zone_path) }
|
||||
}
|
||||
|
||||
pub fn new_if_exists<P: AsRef<std::path::Path>>(zone_path: P) -> Option<Self> {
|
||||
if zone_path.as_ref().exists() {
|
||||
Some(Self { zone: BasicEntityPath::new(zone_path) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Adapter for ThermalZoneSensor {
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::default::Default;
|
|||
use std::convert::{Into, From};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::json::{SettingsJson, GraphPointJson, BoundsJson};
|
||||
use super::json::{SettingsJson, GraphPointJson};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Settings {
|
||||
|
@ -10,8 +10,6 @@ pub struct Settings {
|
|||
pub enable: bool,
|
||||
pub interpolate: bool,
|
||||
pub curve: Vec<GraphPoint>,
|
||||
pub fan_bounds: Bounds<f64>,
|
||||
pub temperature_bounds: Bounds<f64>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
|
@ -28,42 +26,18 @@ impl From<SettingsJson> for Settings {
|
|||
enable: other.enable,
|
||||
interpolate: other.interpolate,
|
||||
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||
fan_bounds: Bounds {
|
||||
min: 1.0,
|
||||
max: 7000.0,
|
||||
},
|
||||
temperature_bounds: Bounds {
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
},
|
||||
},
|
||||
1 => Self {
|
||||
version: 1,
|
||||
enable: other.enable,
|
||||
interpolate: other.interpolate,
|
||||
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||
fan_bounds: other.fan_bounds.map(|x| Bounds::<f64>::from_json(x, other.version)).unwrap_or(Bounds {
|
||||
min: 1.0,
|
||||
max: 7000.0,
|
||||
}),
|
||||
temperature_bounds: other.temperature_bounds.map(|x| Bounds::<f64>::from_json(x, other.version)).unwrap_or(Bounds {
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
}),
|
||||
},
|
||||
_ => Self {
|
||||
version: 1,
|
||||
enable: other.enable,
|
||||
interpolate: other.interpolate,
|
||||
curve: other.curve.drain(..).map(|x| GraphPoint::from_json(x, other.version)).collect(),
|
||||
fan_bounds: Bounds {
|
||||
min: 1.0,
|
||||
max: 7000.0,
|
||||
},
|
||||
temperature_bounds: Bounds {
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
},
|
||||
}
|
||||
};
|
||||
result.sort_curve();
|
||||
|
@ -79,8 +53,6 @@ impl Into<SettingsJson> for Settings {
|
|||
enable: self.enable,
|
||||
interpolate: self.interpolate,
|
||||
curve: self.curve.drain(..).map(|x| x.into()).collect(),
|
||||
fan_bounds: Some(self.fan_bounds.into()),
|
||||
temperature_bounds: Some(self.temperature_bounds.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,72 +93,6 @@ impl Into<GraphPointJson> for GraphPoint {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Bounds<T: core::fmt::Debug + Clone> {
|
||||
pub min: T,
|
||||
pub max: T,
|
||||
}
|
||||
|
||||
/*impl Bounds<usize> {
|
||||
#[inline]
|
||||
pub fn from_json(other: BoundsJson, version: u64) -> Self {
|
||||
match version {
|
||||
0 => Self {
|
||||
min: other.min as _,
|
||||
max: other.max as _,
|
||||
},
|
||||
1 => Self {
|
||||
min: other.min as _,
|
||||
max: other.max as _,
|
||||
},
|
||||
_ => Self {
|
||||
min: other.min as _,
|
||||
max: other.max as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl Bounds<f64> {
|
||||
#[inline]
|
||||
pub fn from_json(other: BoundsJson, version: u64) -> Self {
|
||||
match version {
|
||||
0 => Self {
|
||||
min: other.min,
|
||||
max: other.max,
|
||||
},
|
||||
1 => Self {
|
||||
min: other.min,
|
||||
max: other.max,
|
||||
},
|
||||
_ => Self {
|
||||
min: other.min,
|
||||
max: other.max,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*impl Into<BoundsJson> for Bounds<usize> {
|
||||
#[inline]
|
||||
fn into(self) -> BoundsJson {
|
||||
BoundsJson {
|
||||
min: self.min as _,
|
||||
max: self.max as _,
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl Into<BoundsJson> for Bounds<f64> {
|
||||
#[inline]
|
||||
fn into(self) -> BoundsJson {
|
||||
BoundsJson {
|
||||
min: self.min,
|
||||
max: self.max,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
pub home: PathBuf,
|
||||
|
|
|
@ -11,8 +11,6 @@ pub struct SettingsJson {
|
|||
pub enable: bool,
|
||||
pub interpolate: bool,
|
||||
pub curve: Vec<GraphPointJson>,
|
||||
pub fan_bounds: Option<BoundsJson>,
|
||||
pub temperature_bounds: Option<BoundsJson>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -21,12 +19,6 @@ pub struct GraphPointJson {
|
|||
pub y: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct BoundsJson {
|
||||
pub min: f64,
|
||||
pub max: f64,
|
||||
}
|
||||
|
||||
impl Default for SettingsJson {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -34,14 +26,6 @@ impl Default for SettingsJson {
|
|||
enable: false,
|
||||
interpolate: true,
|
||||
curve: Vec::new(),
|
||||
fan_bounds: Some(BoundsJson {
|
||||
min: 0.0,
|
||||
max: 7000.0,
|
||||
}),
|
||||
temperature_bounds: Some(BoundsJson {
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,9 +32,17 @@ fn main() -> Result<(), ()> {
|
|||
api::FanService::new(control::ControlRuntime::new_boxed(
|
||||
adapters::fans::SteamDeckFan::maybe_find()
|
||||
.map(|f| Box::new(f) as Box<dyn adapters::FanAdapter>)
|
||||
.or_else(
|
||||
|| adapters::fans::RogAllyFan::maybe_find()
|
||||
.map(|f| Box::new(f) as Box<dyn adapters::FanAdapter>)
|
||||
)
|
||||
.unwrap_or_else(|| Box::new(adapters::fans::DevModeFan)),
|
||||
adapters::fans::SteamDeckFan::maybe_find_thermal_zone()
|
||||
.map(|t| Box::new(adapters::sensors::ThermalZoneSensor::new(t)) as Box<dyn adapters::SensorAdapter>)
|
||||
.or_else(
|
||||
|| adapters::sensors::ThermalZoneSensor::new_if_exists("/sys/thermal/thermal_zone0")
|
||||
.map(|t| Box::new(t) as Box<dyn adapters::SensorAdapter>)
|
||||
)
|
||||
.unwrap_or_else(|| Box::new(adapters::sensors::DevModeSensor))
|
||||
))
|
||||
))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Fantastic",
|
||||
"version": "0.5.1-alpha1",
|
||||
"version": "0.6.0-alpha1",
|
||||
"description": "A template to quickly create decky plugins from scratch, based on TypeScript and webpack",
|
||||
"scripts": {
|
||||
"build": "shx rm -rf dist && rollup -c",
|
||||
|
|
Loading…
Reference in a new issue