From 364d9650f210cb7f8163a9ebe1a05cfed4ffa6de Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sat, 14 Sep 2024 13:00:16 -0400 Subject: [PATCH] Add Asus ROG Ally fan support (without interpolation) --- backend-rs/Cargo.lock | 2 +- backend-rs/Cargo.toml | 2 +- backend-rs/src/adapters/fans/mod.rs | 3 + .../src/adapters/fans/rog_ally/adapter.rs | 159 ++++++++++++++++++ backend-rs/src/adapters/fans/rog_ally/mod.rs | 2 + .../src/adapters/fans/steam_deck/adapter.rs | 10 +- .../src/adapters/sensors/thermal_zone.rs | 8 + backend-rs/src/datastructs.rs | 96 +---------- backend-rs/src/json.rs | 16 -- backend-rs/src/main.rs | 8 + package.json | 2 +- 11 files changed, 191 insertions(+), 117 deletions(-) create mode 100644 backend-rs/src/adapters/fans/rog_ally/adapter.rs create mode 100644 backend-rs/src/adapters/fans/rog_ally/mod.rs diff --git a/backend-rs/Cargo.lock b/backend-rs/Cargo.lock index 2632125..6c96431 100644 --- a/backend-rs/Cargo.lock +++ b/backend-rs/Cargo.lock @@ -1243,7 +1243,7 @@ dependencies = [ [[package]] name = "sysfuss" -version = "0.3.0" +version = "0.4.0" [[package]] name = "tempfile" diff --git a/backend-rs/Cargo.toml b/backend-rs/Cargo.toml index 39ffa29..87ab7a5 100644 --- a/backend-rs/Cargo.toml +++ b/backend-rs/Cargo.toml @@ -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" diff --git a/backend-rs/src/adapters/fans/mod.rs b/backend-rs/src/adapters/fans/mod.rs index 42d01f5..c762079 100644 --- a/backend-rs/src/adapters/fans/mod.rs +++ b/backend-rs/src/adapters/fans/mod.rs @@ -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; diff --git a/backend-rs/src/adapters/fans/rog_ally/adapter.rs b/backend-rs/src/adapters/fans/rog_ally/adapter.rs new file mode 100644 index 0000000..cc55263 --- /dev/null +++ b/backend-rs/src/adapters/fans/rog_ally/adapter.rs @@ -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 { + 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 { + 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::(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) +} diff --git a/backend-rs/src/adapters/fans/rog_ally/mod.rs b/backend-rs/src/adapters/fans/rog_ally/mod.rs new file mode 100644 index 0000000..9a2af91 --- /dev/null +++ b/backend-rs/src/adapters/fans/rog_ally/mod.rs @@ -0,0 +1,2 @@ +mod adapter; +pub use adapter::RogAllyFan; diff --git a/backend-rs/src/adapters/fans/steam_deck/adapter.rs b/backend-rs/src/adapters/fans/steam_deck/adapter.rs index 398bac2..98e47da 100644 --- a/backend-rs/src/adapters/fans/steam_deck/adapter.rs +++ b/backend-rs/src/adapters/fans/steam_deck/adapter.rs @@ -174,6 +174,10 @@ fn read_fan(hwmon: &HwMonPath) -> std::io::Result { } } +fn fan_range() -> std::ops::Range { + 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); } diff --git a/backend-rs/src/adapters/sensors/thermal_zone.rs b/backend-rs/src/adapters/sensors/thermal_zone.rs index 1f7d919..d45d2c7 100644 --- a/backend-rs/src/adapters/sensors/thermal_zone.rs +++ b/backend-rs/src/adapters/sensors/thermal_zone.rs @@ -9,6 +9,14 @@ impl ThermalZoneSensor { pub fn new>(zone_path: P) -> Self { Self { zone: BasicEntityPath::new(zone_path) } } + + pub fn new_if_exists>(zone_path: P) -> Option { + if zone_path.as_ref().exists() { + Some(Self { zone: BasicEntityPath::new(zone_path) }) + } else { + None + } + } } impl super::Adapter for ThermalZoneSensor { diff --git a/backend-rs/src/datastructs.rs b/backend-rs/src/datastructs.rs index c9973f5..6e67874 100644 --- a/backend-rs/src/datastructs.rs +++ b/backend-rs/src/datastructs.rs @@ -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, - pub fan_bounds: Bounds, - pub temperature_bounds: Bounds, } impl Settings { @@ -28,42 +26,18 @@ impl From 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::::from_json(x, other.version)).unwrap_or(Bounds { - min: 1.0, - max: 7000.0, - }), - temperature_bounds: other.temperature_bounds.map(|x| Bounds::::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 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 for GraphPoint { } } -#[derive(Debug, Clone)] -pub struct Bounds { - pub min: T, - pub max: T, -} - -/*impl Bounds { - #[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 { - #[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 for Bounds { - #[inline] - fn into(self) -> BoundsJson { - BoundsJson { - min: self.min as _, - max: self.max as _, - } - } -}*/ - -impl Into for Bounds { - #[inline] - fn into(self) -> BoundsJson { - BoundsJson { - min: self.min, - max: self.max, - } - } -} - #[derive(Debug)] pub struct State { pub home: PathBuf, diff --git a/backend-rs/src/json.rs b/backend-rs/src/json.rs index 363395f..1d6ebea 100644 --- a/backend-rs/src/json.rs +++ b/backend-rs/src/json.rs @@ -11,8 +11,6 @@ pub struct SettingsJson { pub enable: bool, pub interpolate: bool, pub curve: Vec, - pub fan_bounds: Option, - pub temperature_bounds: Option, } #[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, - }) } } } diff --git a/backend-rs/src/main.rs b/backend-rs/src/main.rs index d88397f..341c57d 100644 --- a/backend-rs/src/main.rs +++ b/backend-rs/src/main.rs @@ -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) + .or_else( + || adapters::fans::RogAllyFan::maybe_find() + .map(|f| Box::new(f) as Box) + ) .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) + .or_else( + || adapters::sensors::ThermalZoneSensor::new_if_exists("/sys/thermal/thermal_zone0") + .map(|t| Box::new(t) as Box) + ) .unwrap_or_else(|| Box::new(adapters::sensors::DevModeSensor)) )) )) diff --git a/package.json b/package.json index 6fc8a2c..a50b16e 100644 --- a/package.json +++ b/package.json @@ -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",