Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
924f1aaa51 | |||
364d9650f2 | |||
dd0c3998f0 | |||
a0f565895b |
38 changed files with 1231 additions and 884 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -42,9 +42,8 @@ yalc.lock
|
|||
|
||||
# ignore Rust compiler files
|
||||
/backend-rs/target
|
||||
backend
|
||||
/bin/backend
|
||||
/backend/target
|
||||
/backend/out
|
||||
/src/rust/target
|
||||
|
||||
# packaged teasers
|
||||
|
|
722
backend/Cargo.lock → backend-rs/Cargo.lock
generated
722
backend/Cargo.lock → backend-rs/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "fantastic-rs"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0-alpha1"
|
||||
edition = "2021"
|
||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||
description = "Backend (superuser) functionality for Fantastic"
|
||||
|
@ -11,22 +11,23 @@ readme = "../README.md"
|
|||
|
||||
|
||||
[dependencies]
|
||||
usdpl-back = { version = "0.11", features = ["blocking", "decky"]}
|
||||
usdpl-back = { version = "1.0", features = ["blocking"], path = "../../usdpl-rs/usdpl-back"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
nrpc = { version = "0.10", default-features = false }
|
||||
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"] }
|
||||
sysfuss = { version = "0.4", features = ["derive"], path = "../../sysfs-nav" }
|
||||
sysinfo = "0.31"
|
||||
|
||||
# logging
|
||||
log = "0.4"
|
||||
simplelog = "0.12"
|
||||
|
||||
[build-dependencies]
|
||||
usdpl-build = { version = "0.11" }
|
||||
usdpl-build = { version = "1.0", path = "../../usdpl-rs/usdpl-build" }
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
13
backend-rs/build.sh
Executable file
13
backend-rs/build.sh
Executable file
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
#cargo build --release --target x86_64-unknown-linux-musl
|
||||
#cargo build --target x86_64-unknown-linux-musl
|
||||
#cross build --release
|
||||
cargo build
|
||||
|
||||
mkdir -p ../bin
|
||||
#cp --preserve=mode ./target/x86_64-unknown-linux-musl/release/fantastic-rs ../bin/backend
|
||||
#cp --preserve=mode ./target/x86_64-unknown-linux-musl/debug/fantastic-rs ../bin/backend
|
||||
#cp --preserve=mode ./target/release/fantastic-rs ../bin/backend
|
||||
cp --preserve=mode ./target/debug/fantastic-rs ../bin/backend
|
||||
|
11
backend-rs/src/adapters/fans/dev_mode.rs
Normal file
11
backend-rs/src/adapters/fans/dev_mode.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
pub struct DevModeFan;
|
||||
|
||||
impl super::Adapter for DevModeFan {
|
||||
fn on_enable_toggled(&self, settings: &crate::datastructs::Settings) {
|
||||
log::info!("on_enable_toggled invoked with settings {:?}", settings);
|
||||
}
|
||||
|
||||
fn control_fan(&self, settings: &crate::datastructs::Settings, sensor: &crate::adapters::traits::SensorReading) {
|
||||
log::info!("control_fan invoked with settings {:?} and sensor {:?}", settings, sensor);
|
||||
}
|
||||
}
|
10
backend-rs/src/adapters/fans/mod.rs
Normal file
10
backend-rs/src/adapters/fans/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
mod dev_mode;
|
||||
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;
|
297
backend-rs/src/adapters/fans/steam_deck/adapter.rs
Normal file
297
backend-rs/src/adapters/fans/steam_deck/adapter.rs
Normal file
|
@ -0,0 +1,297 @@
|
|||
use sysfuss::{HwMonPath, HwMonAttribute, HwMonAttributeType, HwMonAttributeItem, BasicEntityPath};
|
||||
use sysfuss::{SysPath, capability::attributes, SysEntityAttributesExt};
|
||||
|
||||
use crate::datastructs::{Settings, GraphPoint};
|
||||
|
||||
const VALVE_FAN_SERVICE: &str = "jupiter-fan-control.service";
|
||||
|
||||
pub struct SteamDeckFan {
|
||||
hwmon: HwMonPath,
|
||||
}
|
||||
|
||||
impl SteamDeckFan {
|
||||
pub fn maybe_find() -> Option<Self> {
|
||||
find_hwmon(crate::sys::SYSFS_ROOT).map(|hwmon| Self { hwmon })
|
||||
}
|
||||
|
||||
pub fn maybe_find_thermal_zone() -> Option<BasicEntityPath> {
|
||||
find_thermal_zone(crate::sys::SYSFS_ROOT)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::Adapter for SteamDeckFan {
|
||||
fn on_enable_toggled(&self, settings: &Settings) {
|
||||
on_set_enable(settings, &self.hwmon)
|
||||
}
|
||||
|
||||
fn control_fan(&self, settings: &Settings, sensor: &crate::adapters::traits::SensorReading) {
|
||||
enforce_jupiter_status(true);
|
||||
do_fan_control(settings, &self.hwmon, sensor.value)
|
||||
}
|
||||
|
||||
fn sensor<'a: 'b, 'b>(&'a self) -> Option<&'b dyn crate::adapters::SensorAdapter> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::super::SensorAdapter for SteamDeckFan {
|
||||
fn read(&self) -> Result<crate::adapters::SensorReading, std::io::Error> {
|
||||
Ok(crate::adapters::SensorReading {
|
||||
value: read_fan(&self.hwmon)? as f64,
|
||||
meaning: crate::adapters::SensorType::Fan,
|
||||
name: "Steam Deck Fan",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const RECALCULATE_ATTR: HwMonAttribute = HwMonAttribute::custom("recalculate");
|
||||
const FAN1_INPUT_ATTR: HwMonAttribute = HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Input);
|
||||
const FAN1_LABEL_ATTR: HwMonAttribute = HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Label);
|
||||
const FAN1_TARGET_ATTR: HwMonAttribute = HwMonAttribute::custom("fan1_target");
|
||||
|
||||
const HWMON_NEEDS: [HwMonAttribute; 3] = [
|
||||
//RECALCULATE_ATTR,
|
||||
FAN1_INPUT_ATTR,
|
||||
FAN1_TARGET_ATTR,
|
||||
FAN1_LABEL_ATTR,
|
||||
];
|
||||
|
||||
fn find_hwmon<P: AsRef<std::path::Path>>(path: P) -> Option<HwMonPath> {
|
||||
let syspath = SysPath::path(path);
|
||||
match syspath.hwmon(attributes(HWMON_NEEDS.into_iter()))
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs hwmon iter error while finding Steam Deck fan: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(mut iter) => {
|
||||
if let Some(entity) = iter.next() {
|
||||
log::info!("Found Steam Deck fan hwmon {}", entity.as_ref().display());
|
||||
Some(entity)
|
||||
} else {
|
||||
log::error!("sysfs hwmon iter empty while finding Steam Deck fan: [no capable results]");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_thermal_zone<P: AsRef<std::path::Path>>(path: P) -> Option<BasicEntityPath> {
|
||||
let syspath = SysPath::path(path);
|
||||
match syspath.class("thermal",
|
||||
|ent: &BasicEntityPath| ent.exists(&"temp".to_owned()) && ent.exists(&"type".to_owned()) && ent.attribute("type".to_owned()).map(|val: String| val.to_lowercase() != "processor").unwrap_or(false))
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs thermal class iter error while finding Steam Deck thermal zone: {}", e);
|
||||
None
|
||||
},
|
||||
Ok(mut iter) => {
|
||||
if let Some(entity) = iter.next() {
|
||||
log::info!("Found thermal zone {}", entity.as_ref().display());
|
||||
Some(entity)
|
||||
} else {
|
||||
log::error!("sysfs thermal class iter empty while finding Steam Deck thermal zone: [no capable results]");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_set_enable(settings: &Settings, hwmon: &HwMonPath) {
|
||||
// stop/start jupiter fan control (since the client-side way of doing this was removed :( )
|
||||
enforce_jupiter_status(settings.enable);
|
||||
if let Err(e) = write_fan_recalc(hwmon, settings.enable) {
|
||||
log::error!("runtime failed to write to fan recalculate file: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn enforce_jupiter_status(enabled: bool) {
|
||||
// enabled refers to whether this plugin's functionality is enabled,
|
||||
// not the jupiter fan control service
|
||||
let service_status = detect_jupiter_fan_service();
|
||||
log::debug!("fan control service is enabled? {}", service_status);
|
||||
if enabled == service_status {
|
||||
// do not run Valve's fan service along with Fantastic, since they fight
|
||||
if enabled {
|
||||
stop_fan_service();
|
||||
} else {
|
||||
start_fan_service();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_jupiter_fan_service() -> bool {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["is-active", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Ok(cmd) => String::from_utf8_lossy(&cmd.stdout).trim() == "active",
|
||||
Err(e) => {
|
||||
log::error!("`systemctl is-active {}` err: {}", VALVE_FAN_SERVICE, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_fan_service() {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["start", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Err(e) => log::error!("`systemctl start {}` err: {}", VALVE_FAN_SERVICE, e),
|
||||
Ok(out) => log::debug!("started `{}`:\nstdout:{}\nstderr:{}", VALVE_FAN_SERVICE, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr)),
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_fan_service() {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["stop", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Err(e) => log::error!("`systemctl stop {}` err: {}", VALVE_FAN_SERVICE, e),
|
||||
Ok(out) => log::debug!("stopped `{}`:\nstdout:{}\nstderr:{}", VALVE_FAN_SERVICE, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr)),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_fan_recalc(hwmon: &HwMonPath, enabled: bool) -> Result<(), std::io::Error> {
|
||||
hwmon.set(RECALCULATE_ATTR, enabled as u8)
|
||||
//write_single(format!("/sys/class/hwmon/hwmon{}/recalculate", HWMON_INDEX), enabled as u8)
|
||||
}
|
||||
|
||||
fn write_fan_target(hwmon: &HwMonPath, rpm: u64) -> Result<(), std::io::Error> {
|
||||
hwmon.set(FAN1_TARGET_ATTR, rpm)
|
||||
//write_single(format!("/sys/class/hwmon/hwmon{}/fan1_target", HWMON_INDEX), rpm)
|
||||
}
|
||||
|
||||
fn read_fan(hwmon: &HwMonPath) -> std::io::Result<u64> {
|
||||
match hwmon.attribute(FAN1_INPUT_ATTR){
|
||||
Ok(x) => Ok(x),
|
||||
Err(sysfuss::EitherErr2::First(e)) => {
|
||||
log::error!("Failed Steam Deck read_fan(): {}", e);
|
||||
Err(e)
|
||||
},
|
||||
Err(sysfuss::EitherErr2::Second(e)) => {
|
||||
log::error!("Failed Steam Deck read_fan(): {}", e);
|
||||
Err(std::io::Error::other(e))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn fan_range() -> std::ops::Range<f64> {
|
||||
let sys = sysinfo::System::new_with_specifics(
|
||||
sysinfo::RefreshKind::new().with_cpu(sysinfo::CpuRefreshKind::everything()),
|
||||
);
|
||||
if sys.cpus().is_empty() {
|
||||
1.0..7123.0
|
||||
} else {
|
||||
let cpu_name = sys.cpus()[0].brand();
|
||||
if cpu_name.contains("AMD Custom APU 0405") {
|
||||
// LCD
|
||||
1.0..7000.0
|
||||
} else if cpu_name.contains("AMD Custom APU 0932") {
|
||||
// OLED
|
||||
1.0..9000.0
|
||||
} else {
|
||||
// Hopefully never reached
|
||||
1.0..7001.0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn do_fan_control(settings: &Settings, hwmon: &HwMonPath, thermal_zone: f64) {
|
||||
/*
|
||||
curve = self.settings["curve"]
|
||||
fan_ratio = 0 # unnecessary in Python, but stupid without
|
||||
if len(curve) == 0:
|
||||
fan_ratio = 1
|
||||
else:
|
||||
index = -1
|
||||
temperature_ratio = (thermal_zone(0) - TEMPERATURE_MINIMUM) / (TEMPERATURE_MAXIMUM - TEMPERATURE_MINIMUM)
|
||||
for i in range(len(curve)-1, -1, -1):
|
||||
if curve[i]["x"] < temperature_ratio:
|
||||
index = i
|
||||
break
|
||||
if self.settings["interpolate"]:
|
||||
fan_ratio = self.interpolate_fan(self, index, temperature_ratio)
|
||||
else:
|
||||
fan_ratio = self.step_fan(self, index, temperature_ratio)
|
||||
set_fan_target(int((fan_ratio * FAN_MAXIMUM) + FAN_MINIMUM))
|
||||
*/
|
||||
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 {
|
||||
index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let fan_ratio = if settings.interpolate {
|
||||
interpolate_fan(settings, index, temperature_ratio)
|
||||
} else {
|
||||
step_fan(settings, index, temperature_ratio)
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate_fan(settings: &Settings, index: Option<usize>, t_ratio: f64) -> f64 {
|
||||
/*
|
||||
curve = self.settings["curve"]
|
||||
upper_point = {"x": 1.0, "y": 0.0}
|
||||
lower_point = {"x": 0.0, "y": 1.0}
|
||||
if index != -1: # guaranteed to not be empty
|
||||
lower_point = curve[index]
|
||||
if index != len(curve) - 1:
|
||||
upper_point = curve[index+1]
|
||||
#logging.debug(f"lower_point: {lower_point}, upper_point: {upper_point}")
|
||||
upper_y = 1-upper_point["y"]
|
||||
lower_y = 1-lower_point["y"]
|
||||
slope_m = (upper_y - lower_y) / (upper_point["x"] - lower_point["x"])
|
||||
y_intercept_b = lower_y - (slope_m * lower_point["x"])
|
||||
logging.debug(f"interpolation: y = {slope_m}x + {y_intercept_b}")
|
||||
return (slope_m * temperature_ratio) + y_intercept_b
|
||||
*/
|
||||
let (upper, lower) = if let Some(i) = index {
|
||||
(if i != settings.curve.len() - 1 {
|
||||
settings.curve[i+1].clone()
|
||||
} else {
|
||||
GraphPoint{x: 1.0, y: 1.0}
|
||||
},
|
||||
settings.curve[i].clone())
|
||||
} else {
|
||||
(if settings.curve.is_empty() {
|
||||
GraphPoint{x: 1.0, y: 1.0}
|
||||
} else {
|
||||
settings.curve[0].clone()
|
||||
},
|
||||
GraphPoint{x: 0.0, y: 0.0})
|
||||
};
|
||||
let slope_m = (upper.y - lower.y) / (upper.x - lower.x);
|
||||
let y_intercept_b = lower.y - (slope_m * lower.x);
|
||||
log::debug!("interpolation: y = {}x + {} (between {:?} and {:?})", slope_m, y_intercept_b, upper, lower);
|
||||
(slope_m * t_ratio) + y_intercept_b
|
||||
}
|
||||
|
||||
fn step_fan(settings: &Settings, index: Option<usize>, _t_ratio: f64) -> f64 {
|
||||
/*
|
||||
curve = self.settings["curve"]
|
||||
if index != -1:
|
||||
return 1 - curve[index]["y"]
|
||||
else:
|
||||
if len(curve) == 0:
|
||||
return 1
|
||||
else:
|
||||
return 0.5
|
||||
*/
|
||||
// step fan, what are you doing?
|
||||
if let Some(index) = index {
|
||||
settings.curve[index].y
|
||||
} else {
|
||||
if settings.curve.is_empty() {
|
||||
1.0
|
||||
} else {
|
||||
0.5
|
||||
}
|
||||
}
|
||||
}
|
2
backend-rs/src/adapters/fans/steam_deck/mod.rs
Normal file
2
backend-rs/src/adapters/fans/steam_deck/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod adapter;
|
||||
pub use adapter::SteamDeckFan;
|
5
backend-rs/src/adapters/mod.rs
Normal file
5
backend-rs/src/adapters/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod traits;
|
||||
pub use traits::{FanAdapter, SensorAdapter, SensorReading, SensorType};
|
||||
|
||||
pub mod fans;
|
||||
pub mod sensors;
|
12
backend-rs/src/adapters/sensors/dev_mode.rs
Normal file
12
backend-rs/src/adapters/sensors/dev_mode.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
pub struct DevModeSensor;
|
||||
|
||||
impl super::Adapter for DevModeSensor {
|
||||
fn read(&self) -> Result<super::Reading, std::io::Error> {
|
||||
log::info!("read invoked");
|
||||
return Ok(super::Reading {
|
||||
value: 42_000.0,
|
||||
meaning: super::super::SensorType::Temperature,
|
||||
name: "DevModeSensor",
|
||||
})
|
||||
}
|
||||
}
|
8
backend-rs/src/adapters/sensors/mod.rs
Normal file
8
backend-rs/src/adapters/sensors/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
mod dev_mode;
|
||||
pub use dev_mode::DevModeSensor;
|
||||
|
||||
mod thermal_zone;
|
||||
pub use thermal_zone::ThermalZoneSensor;
|
||||
|
||||
pub(self) use super::SensorAdapter as Adapter;
|
||||
pub(self) use super::SensorReading as Reading;
|
44
backend-rs/src/adapters/sensors/thermal_zone.rs
Normal file
44
backend-rs/src/adapters/sensors/thermal_zone.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use sysfuss::BasicEntityPath;
|
||||
use sysfuss::SysEntityAttributesExt;
|
||||
|
||||
pub struct ThermalZoneSensor {
|
||||
zone: BasicEntityPath,
|
||||
}
|
||||
|
||||
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 {
|
||||
fn read(&self) -> Result<super::Reading, std::io::Error> {
|
||||
Ok(super::Reading {
|
||||
value: read_thermal_zone(&self.zone)? as f64,
|
||||
meaning: super::super::SensorType::Temperature,
|
||||
name: "thermal_zone",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn read_thermal_zone(entity: &BasicEntityPath) -> std::io::Result<u64> {
|
||||
match entity.attribute("temp".to_owned()) {
|
||||
Ok(x) => Ok(x),
|
||||
Err(sysfuss::EitherErr2::First(e)) => {
|
||||
log::error!("Failed read_thermal_zone(): {}", e);
|
||||
Err(e)
|
||||
},
|
||||
Err(sysfuss::EitherErr2::Second(e)) => {
|
||||
log::error!("Failed read_thermal_zone(): {}", e);
|
||||
Err(std::io::Error::other(e))
|
||||
},
|
||||
}
|
||||
}
|
30
backend-rs/src/adapters/traits.rs
Normal file
30
backend-rs/src/adapters/traits.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::datastructs::Settings;
|
||||
|
||||
pub trait FanAdapter: Send + Sync {
|
||||
/// Handle fan enable UI toggle
|
||||
fn on_enable_toggled(&self, settings: &Settings);
|
||||
/// Apply fan settings to fan (probably through udev/sysfs)
|
||||
fn control_fan(&self, settings: &Settings, sensor: &SensorReading);
|
||||
/// Get fan speed sensor
|
||||
fn sensor<'a: 'b, 'b>(&'a self) -> Option<&'b dyn SensorAdapter> { None }
|
||||
}
|
||||
|
||||
pub trait SensorAdapter: Send + Sync {
|
||||
/// Read sensor value
|
||||
fn read(&self) -> Result<SensorReading, std::io::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SensorReading {
|
||||
pub value: f64,
|
||||
pub meaning: SensorType,
|
||||
pub name: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SensorType {
|
||||
Temperature, // milli-degrees Celcius
|
||||
Fan, // revolutions per minute (RPM)
|
||||
#[allow(dead_code)]
|
||||
Unknown,
|
||||
}
|
|
@ -28,7 +28,7 @@ fn once_true() -> impl std::iter::Iterator<Item = bool> {
|
|||
std::iter::once(true).chain(std::iter::repeat(false))
|
||||
}
|
||||
|
||||
#[usdpl_back::nrpc::_helpers::async_trait::async_trait]
|
||||
#[::nrpc::_helpers::async_trait::async_trait]
|
||||
impl<'a> IFan<'a> for FanService {
|
||||
async fn echo(
|
||||
&mut self,
|
||||
|
@ -84,22 +84,33 @@ impl<'a> IFan<'a> for FanService {
|
|||
usdpl_back::nrpc::ServiceServerStream<'b, RpmMessage>,
|
||||
Box<dyn std::error::Error + Send>,
|
||||
> {
|
||||
let hwmon = self.ctrl.hwmon_clone();
|
||||
let stream = usdpl_back::nrpc::_helpers::futures::stream::iter(once_true()).then(move |is_first| {
|
||||
let hwmon = hwmon.clone();
|
||||
tokio::task::spawn_blocking(
|
||||
/* tokio::time::sleep(..) is not Unpin (but this is)... *grumble grumble* */
|
||||
move || if !is_first { std::thread::sleep(FAN_READ_PERIOD); })
|
||||
.map(move |_| {
|
||||
if let Some(rpm) = crate::sys::read_fan(&hwmon) {
|
||||
log::debug!("get_fan_rpm() success: {}", rpm);
|
||||
Ok(RpmMessage { rpm: rpm as u32 })
|
||||
} else {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from("Failed to read fan speed")))
|
||||
}
|
||||
})
|
||||
});
|
||||
Ok(Box::new(stream))
|
||||
let fan_clone = self.ctrl.fan_clone();
|
||||
if fan_clone.sensor().is_some() {
|
||||
let stream = usdpl_back::nrpc::_helpers::futures::stream::iter(once_true()).then(move |is_first| {
|
||||
let fan_clone2 = fan_clone.clone();
|
||||
tokio::task::spawn_blocking(
|
||||
/* tokio::time::sleep(..) is not Unpin (but this is)... *grumble grumble* */
|
||||
move || if !is_first { std::thread::sleep(FAN_READ_PERIOD); })
|
||||
.map(move |_| {
|
||||
if let Some(fan_sensor) = fan_clone2.sensor() {
|
||||
match fan_sensor.read() {
|
||||
Ok(reading) => {
|
||||
log::debug!("get_fan_rpm() success: {}", reading.value);
|
||||
Ok(RpmMessage { rpm: reading.value as u32 })
|
||||
}
|
||||
Err(e) => {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from(format!("Failed to read fan speed: {}", e))))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from("Failed to get fan speed sensor")))
|
||||
}
|
||||
})
|
||||
});
|
||||
Ok(Box::new(stream))
|
||||
} else {
|
||||
Ok(Box::new(usdpl_back::nrpc::_helpers::futures::stream::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_temperature<'b: 'a>(
|
||||
|
@ -109,19 +120,22 @@ impl<'a> IFan<'a> for FanService {
|
|||
usdpl_back::nrpc::ServiceServerStream<'b, TemperatureMessage>,
|
||||
Box<dyn std::error::Error + Send>,
|
||||
> {
|
||||
let thermal_zone = self.ctrl.thermal_zone_clone();
|
||||
let sensor_clone = self.ctrl.sensor_clone();
|
||||
let stream = usdpl_back::nrpc::_helpers::futures::stream::iter(once_true()).then(move |is_first| {
|
||||
let thermal_zone = thermal_zone.clone();
|
||||
let sensor_clone2 = sensor_clone.clone();
|
||||
tokio::task::spawn_blocking(
|
||||
/* tokio::time::sleep(..) is not Unpin (but this is)... *grumble grumble* */
|
||||
move || if !is_first { std::thread::sleep(TEMPERATURE_READ_PERIOD); })
|
||||
.map(move |_| {
|
||||
if let Some(temperature) = crate::sys::read_thermal_zone(&thermal_zone) {
|
||||
let real_temp = temperature as f64 / 1000.0;
|
||||
log::debug!("get_temperature() success: {}", real_temp);
|
||||
Ok(TemperatureMessage { temperature: real_temp })
|
||||
} else {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from("get_temperature failed to read thermal zone 0")))
|
||||
match sensor_clone2.read() {
|
||||
Ok(reading) => {
|
||||
let real_temp = reading.value as f64 / 1000.0;
|
||||
log::debug!("get_temperature() success: {}", real_temp);
|
||||
Ok(TemperatureMessage { temperature: real_temp })
|
||||
},
|
||||
Err(e) => {
|
||||
Err(usdpl_back::nrpc::ServiceError::Method(Box::<dyn std::error::Error + Send + Sync>::from(format!("get_temperature failed to read sensor: {}", e))))
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
115
backend-rs/src/control.rs
Normal file
115
backend-rs/src/control.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
//! Fan control
|
||||
|
||||
use std::sync::Arc;
|
||||
//use std::collections::HashMap;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::datastructs::{Settings, State};
|
||||
use super::json::SettingsJson;
|
||||
|
||||
pub struct ControlRuntime {
|
||||
settings: Arc<RwLock<Settings>>,
|
||||
state: Arc<RwLock<State>>,
|
||||
fan_adapter: Arc<Box<dyn crate::adapters::FanAdapter + 'static>>,
|
||||
sensor_adapter: Arc<Box<dyn crate::adapters::SensorAdapter + 'static>>,
|
||||
}
|
||||
|
||||
impl ControlRuntime {
|
||||
#[allow(dead_code)]
|
||||
pub fn new<F: crate::adapters::FanAdapter + 'static, S: crate::adapters::SensorAdapter + 'static>(fan: F, sensor: S) -> Self {
|
||||
Self::new_boxed(Box::new(fan), Box::new(sensor))
|
||||
}
|
||||
|
||||
pub(crate) fn new_boxed(fan: Box<dyn crate::adapters::FanAdapter + 'static>, sensor: Box<dyn crate::adapters::SensorAdapter + 'static>) -> Self {
|
||||
let new_state = State::new();
|
||||
let settings_p = settings_path(&new_state.home);
|
||||
Self {
|
||||
settings: Arc::new(RwLock::new(super::json::SettingsJson::open(settings_p).unwrap_or_default().into())),
|
||||
state: Arc::new(RwLock::new(new_state)),
|
||||
fan_adapter: Arc::new(fan),
|
||||
sensor_adapter: Arc::new(sensor),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn settings_clone(&self) -> Arc<RwLock<Settings>> {
|
||||
self.settings.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn state_clone(&self) -> Arc<RwLock<State>> {
|
||||
self.state.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn settings(&self) -> &'_ RwLock<Settings> {
|
||||
&self.settings
|
||||
}
|
||||
|
||||
pub(crate) fn state(&self) -> &'_ RwLock<State> {
|
||||
&self.state
|
||||
}
|
||||
|
||||
pub(crate) fn sensor_clone(&self) -> Arc<Box<dyn crate::adapters::SensorAdapter>> {
|
||||
self.sensor_adapter.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn fan_clone(&self) -> Arc<Box<dyn crate::adapters::FanAdapter>> {
|
||||
self.fan_adapter.clone()
|
||||
}
|
||||
|
||||
pub fn run(&self) -> thread::JoinHandle<()> {
|
||||
let runtime_settings = self.settings_clone();
|
||||
let runtime_state = self.state_clone();
|
||||
let runtime_fan = self.fan_adapter.clone();
|
||||
let runtime_sensor = self.sensor_adapter.clone();
|
||||
thread::spawn(move || {
|
||||
let sleep_duration = Duration::from_millis(1000);
|
||||
let mut start_time = Instant::now();
|
||||
loop {
|
||||
if Instant::now().duration_since(start_time).as_secs_f64() * 0.95 > sleep_duration.as_secs_f64() {
|
||||
// resumed from sleep; do fan re-init
|
||||
log::debug!("Detected resume from sleep, overriding fan again");
|
||||
{
|
||||
let settings = runtime_settings.blocking_read();
|
||||
if settings.enable {
|
||||
runtime_fan.on_enable_toggled(&settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
start_time = Instant::now();
|
||||
{ // save to file
|
||||
let state = runtime_state.blocking_read();
|
||||
if state.dirty {
|
||||
// save settings to file
|
||||
let settings = runtime_settings.blocking_read();
|
||||
let settings_json: SettingsJson = settings.clone().into();
|
||||
if let Err(e) = settings_json.save(settings_path(&state.home)) {
|
||||
log::error!("SettingsJson.save({}) error: {}", settings_path(&state.home).display(), e);
|
||||
}
|
||||
runtime_fan.on_enable_toggled(&settings);
|
||||
drop(state);
|
||||
let mut state = runtime_state.blocking_write();
|
||||
state.dirty = false;
|
||||
}
|
||||
}
|
||||
{ // fan control
|
||||
let settings = runtime_settings.blocking_read();
|
||||
if settings.enable {
|
||||
match runtime_sensor.read() {
|
||||
Err(e) => log::error!("Failed to read sensor for control_fan input: {}", e),
|
||||
Ok(reading) => {
|
||||
runtime_fan.control_fan(&settings, &reading);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thread::sleep(sleep_duration);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_path<P: AsRef<std::path::Path>>(home: P) -> std::path::PathBuf {
|
||||
home.as_ref().join(".config/fantastic/fantastic.json")
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod api;
|
||||
mod adapters;
|
||||
mod control;
|
||||
mod datastructs;
|
||||
mod json;
|
||||
|
@ -28,7 +29,22 @@ fn main() -> Result<(), ()> {
|
|||
println!("Starting back-end ({} v{})", api::NAME, api::VERSION);
|
||||
usdpl_back::Server::new(PORT)
|
||||
.register(FanServer::new(
|
||||
api::FanService::new(control::ControlRuntime::new())
|
||||
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))
|
||||
))
|
||||
))
|
||||
.run_blocking()
|
||||
.unwrap();
|
1
backend-rs/src/sys.rs
Normal file
1
backend-rs/src/sys.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub const SYSFS_ROOT: &str = "/";
|
|
@ -1,7 +0,0 @@
|
|||
FROM ghcr.io/steamdeckhomebrew/holo-toolchain-rust:latest
|
||||
|
||||
#RUN pacman -S --noconfirm cmake make clang git
|
||||
# for building Rust wasm frontend
|
||||
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
ENTRYPOINT [ "/backend/entrypoint.sh" ]
|
|
@ -1,14 +0,0 @@
|
|||
# This is the default target, which will be built when
|
||||
# you invoke make
|
||||
.PHONY: all
|
||||
all: hello
|
||||
|
||||
# This rule tells make how to build hello from hello.cpp
|
||||
hello:
|
||||
mkdir -p ./out
|
||||
gcc -o ./out/hello ./src/main.c
|
||||
|
||||
# This rule tells make to delete hello and hello.o
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f hello
|
|
@ -1,22 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "--- Rust version info ---"
|
||||
rustup --version
|
||||
rustc --version
|
||||
cargo --version
|
||||
|
||||
echo "--- Building plugin backend ---"
|
||||
cargo build --release
|
||||
#mkdir -p out
|
||||
cp target/release/fantastic-rs out/backend
|
||||
|
||||
echo "--- Building plugin frontend (wasm) ---"
|
||||
# this only builds the webassembly part, while the actual frontend task builds the rest of it
|
||||
cd /plugin/src/rust
|
||||
./build.sh decky
|
||||
cargo clean
|
||||
cd /backend
|
||||
|
||||
echo " --- Cleaning up ---"
|
||||
# remove root-owned target folder
|
||||
cargo clean
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cargo build --target x86_64-unknown-linux-musl --release
|
||||
mkdir -p ../bin
|
||||
cp ./target/x86_64-unknown-linux-musl/release/fantastic-rs ../bin/backend
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/bash
|
||||
# build docker container locally (for testing)
|
||||
|
||||
docker build -t fantastic_backend .
|
|
@ -1,8 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "Container's IP address: `awk 'END{print $1}' /etc/hosts`"
|
||||
|
||||
cd /backend
|
||||
|
||||
sudo bash build-docker.sh
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
# run docker container locally (for testing)
|
||||
# assumes you're running in the backend/ dir of the project
|
||||
|
||||
docker run -i --entrypoint /backend/entrypoint.sh -v $PWD:/backend fantastic_backend
|
||||
mkdir -p ../bin
|
||||
cp ./out/backend ../bin
|
|
@ -1,276 +0,0 @@
|
|||
//! Fan control
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use sysfuss::{HwMonPath, BasicEntityPath};
|
||||
|
||||
use super::datastructs::{Settings, State, GraphPoint};
|
||||
use super::json::SettingsJson;
|
||||
|
||||
const VALVE_FAN_SERVICE: &str = "jupiter-fan-control.service";
|
||||
const SYSFS_ROOT: &str = "/";
|
||||
|
||||
pub struct ControlRuntime {
|
||||
settings: Arc<RwLock<Settings>>,
|
||||
state: Arc<RwLock<State>>,
|
||||
hwmon: Arc<HwMonPath>,
|
||||
thermal_zone: Arc<BasicEntityPath>,
|
||||
}
|
||||
|
||||
impl ControlRuntime {
|
||||
pub fn new() -> Self {
|
||||
let new_state = State::new();
|
||||
let settings_p = settings_path(&new_state.home);
|
||||
Self {
|
||||
settings: Arc::new(RwLock::new(super::json::SettingsJson::open(settings_p).unwrap_or_default().into())),
|
||||
state: Arc::new(RwLock::new(new_state)),
|
||||
hwmon: Arc::new(crate::sys::find_hwmon(SYSFS_ROOT)),
|
||||
thermal_zone: Arc::new(crate::sys::find_thermal_zone(SYSFS_ROOT))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn settings_clone(&self) -> Arc<RwLock<Settings>> {
|
||||
self.settings.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn state_clone(&self) -> Arc<RwLock<State>> {
|
||||
self.state.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn settings(&self) -> &'_ RwLock<Settings> {
|
||||
&self.settings
|
||||
}
|
||||
|
||||
pub(crate) fn state(&self) -> &'_ RwLock<State> {
|
||||
&self.state
|
||||
}
|
||||
|
||||
/*pub(crate) fn hwmon(&self) -> &'_ HwMonPath {
|
||||
&self.hwmon
|
||||
}*/
|
||||
|
||||
pub(crate) fn hwmon_clone(&self) -> Arc<HwMonPath> {
|
||||
self.hwmon.clone()
|
||||
}
|
||||
|
||||
/*pub(crate) fn thermal_zone(&self) -> &'_ BasicEntityPath {
|
||||
&self.thermal_zone
|
||||
}*/
|
||||
|
||||
pub(crate) fn thermal_zone_clone(&self) -> Arc<BasicEntityPath> {
|
||||
self.thermal_zone.clone()
|
||||
}
|
||||
|
||||
pub fn run(&self) -> thread::JoinHandle<()> {
|
||||
let runtime_settings = self.settings_clone();
|
||||
let runtime_state = self.state_clone();
|
||||
let runtime_hwmon = self.hwmon.clone();
|
||||
let runtime_thermal_zone = self.thermal_zone.clone();
|
||||
thread::spawn(move || {
|
||||
let sleep_duration = Duration::from_millis(1000);
|
||||
let mut start_time = Instant::now();
|
||||
loop {
|
||||
if Instant::now().duration_since(start_time).as_secs_f64() * 0.95 > sleep_duration.as_secs_f64() {
|
||||
// resumed from sleep; do fan re-init
|
||||
log::debug!("Detected resume from sleep, overriding fan again");
|
||||
{
|
||||
let state = runtime_state.blocking_read();
|
||||
let settings = runtime_settings.blocking_read();
|
||||
if settings.enable {
|
||||
Self::on_set_enable(&settings, &state, &runtime_hwmon);
|
||||
}
|
||||
}
|
||||
}
|
||||
start_time = Instant::now();
|
||||
{ // save to file
|
||||
let state = runtime_state.blocking_read();
|
||||
if state.dirty {
|
||||
// save settings to file
|
||||
let settings = runtime_settings.blocking_read();
|
||||
let settings_json: SettingsJson = settings.clone().into();
|
||||
if let Err(e) = settings_json.save(settings_path(&state.home)) {
|
||||
log::error!("SettingsJson.save({}) error: {}", settings_path(&state.home).display(), e);
|
||||
}
|
||||
Self::on_set_enable(&settings, &state, &runtime_hwmon);
|
||||
drop(state);
|
||||
let mut state = runtime_state.blocking_write();
|
||||
state.dirty = false;
|
||||
}
|
||||
}
|
||||
{ // fan control
|
||||
let settings = runtime_settings.blocking_read();
|
||||
if settings.enable {
|
||||
Self::enforce_jupiter_status(true);
|
||||
Self::do_fan_control(&settings, &runtime_hwmon, &runtime_thermal_zone);
|
||||
}
|
||||
}
|
||||
thread::sleep(sleep_duration);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn on_set_enable(settings: &Settings, _state: &State, hwmon: &HwMonPath) {
|
||||
// stop/start jupiter fan control (since the client-side way of doing this was removed :( )
|
||||
Self::enforce_jupiter_status(settings.enable);
|
||||
if let Err(e) = crate::sys::write_fan_recalc(hwmon, settings.enable) {
|
||||
log::error!("runtime failed to write to fan recalculate file: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_fan_control(settings: &Settings, hwmon: &HwMonPath, thermal_zone: &BasicEntityPath) {
|
||||
/*
|
||||
curve = self.settings["curve"]
|
||||
fan_ratio = 0 # unnecessary in Python, but stupid without
|
||||
if len(curve) == 0:
|
||||
fan_ratio = 1
|
||||
else:
|
||||
index = -1
|
||||
temperature_ratio = (thermal_zone(0) - TEMPERATURE_MINIMUM) / (TEMPERATURE_MAXIMUM - TEMPERATURE_MINIMUM)
|
||||
for i in range(len(curve)-1, -1, -1):
|
||||
if curve[i]["x"] < temperature_ratio:
|
||||
index = i
|
||||
break
|
||||
if self.settings["interpolate"]:
|
||||
fan_ratio = self.interpolate_fan(self, index, temperature_ratio)
|
||||
else:
|
||||
fan_ratio = self.step_fan(self, index, temperature_ratio)
|
||||
set_fan_target(int((fan_ratio * FAN_MAXIMUM) + FAN_MINIMUM))
|
||||
*/
|
||||
let fan_ratio: f64 = if let Some(thermal_zone) = crate::sys::read_thermal_zone(thermal_zone) {
|
||||
let temperature_ratio = (((thermal_zone as f64)/1000.0) - settings.temperature_bounds.min)
|
||||
/ (settings.temperature_bounds.max - settings.temperature_bounds.min);
|
||||
let mut index = None;
|
||||
for i in (0..settings.curve.len()).rev() {
|
||||
if settings.curve[i].x < temperature_ratio {
|
||||
index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if settings.interpolate {
|
||||
Self::interpolate_fan(settings, index, temperature_ratio)
|
||||
} else {
|
||||
Self::step_fan(settings, index, temperature_ratio)
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let fan_speed: u64 = ((fan_ratio * (settings.fan_bounds.max - settings.fan_bounds.min)) + settings.fan_bounds.min) as _;
|
||||
if let Err(e) = crate::sys::write_fan_target(hwmon, fan_speed) {
|
||||
log::error!("runtime failed to write to fan target file: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate_fan(settings: &Settings, index: Option<usize>, t_ratio: f64) -> f64 {
|
||||
/*
|
||||
curve = self.settings["curve"]
|
||||
upper_point = {"x": 1.0, "y": 0.0}
|
||||
lower_point = {"x": 0.0, "y": 1.0}
|
||||
if index != -1: # guaranteed to not be empty
|
||||
lower_point = curve[index]
|
||||
if index != len(curve) - 1:
|
||||
upper_point = curve[index+1]
|
||||
#logging.debug(f"lower_point: {lower_point}, upper_point: {upper_point}")
|
||||
upper_y = 1-upper_point["y"]
|
||||
lower_y = 1-lower_point["y"]
|
||||
slope_m = (upper_y - lower_y) / (upper_point["x"] - lower_point["x"])
|
||||
y_intercept_b = lower_y - (slope_m * lower_point["x"])
|
||||
logging.debug(f"interpolation: y = {slope_m}x + {y_intercept_b}")
|
||||
return (slope_m * temperature_ratio) + y_intercept_b
|
||||
*/
|
||||
let (upper, lower) = if let Some(i) = index {
|
||||
(if i != settings.curve.len() - 1 {
|
||||
settings.curve[i+1].clone()
|
||||
} else {
|
||||
GraphPoint{x: 1.0, y: 1.0}
|
||||
},
|
||||
settings.curve[i].clone())
|
||||
} else {
|
||||
(if settings.curve.is_empty() {
|
||||
GraphPoint{x: 1.0, y: 1.0}
|
||||
} else {
|
||||
settings.curve[0].clone()
|
||||
},
|
||||
GraphPoint{x: 0.0, y: 0.0})
|
||||
};
|
||||
let slope_m = (upper.y - lower.y) / (upper.x - lower.x);
|
||||
let y_intercept_b = lower.y - (slope_m * lower.x);
|
||||
log::debug!("interpolation: y = {}x + {} (between {:?} and {:?})", slope_m, y_intercept_b, upper, lower);
|
||||
(slope_m * t_ratio) + y_intercept_b
|
||||
}
|
||||
|
||||
fn step_fan(settings: &Settings, index: Option<usize>, _t_ratio: f64) -> f64 {
|
||||
/*
|
||||
curve = self.settings["curve"]
|
||||
if index != -1:
|
||||
return 1 - curve[index]["y"]
|
||||
else:
|
||||
if len(curve) == 0:
|
||||
return 1
|
||||
else:
|
||||
return 0.5
|
||||
*/
|
||||
// step fan, what are you doing?
|
||||
if let Some(index) = index {
|
||||
settings.curve[index].y
|
||||
} else {
|
||||
if settings.curve.is_empty() {
|
||||
1.0
|
||||
} else {
|
||||
0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enforce_jupiter_status(enabled: bool) {
|
||||
// enabled refers to whether this plugin's functionality is enabled,
|
||||
// not the jupiter fan control service
|
||||
let service_status = Self::detect_jupiter_fan_service();
|
||||
log::debug!("fan control service is enabled? {}", service_status);
|
||||
if enabled == service_status {
|
||||
// do not run Valve's fan service along with Fantastic, since they fight
|
||||
if enabled {
|
||||
Self::stop_fan_service();
|
||||
} else {
|
||||
Self::start_fan_service();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_jupiter_fan_service() -> bool {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["is-active", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Ok(cmd) => String::from_utf8_lossy(&cmd.stdout).trim() == "active",
|
||||
Err(e) => {
|
||||
log::error!("`systemctl is-active {}` err: {}", VALVE_FAN_SERVICE, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_fan_service() {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["start", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Err(e) => log::error!("`systemctl start {}` err: {}", VALVE_FAN_SERVICE, e),
|
||||
Ok(out) => log::debug!("started `{}`:\nstdout:{}\nstderr:{}", VALVE_FAN_SERVICE, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr)),
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_fan_service() {
|
||||
match std::process::Command::new("systemctl")
|
||||
.args(["stop", VALVE_FAN_SERVICE])
|
||||
.output() {
|
||||
Err(e) => log::error!("`systemctl stop {}` err: {}", VALVE_FAN_SERVICE, e),
|
||||
Ok(out) => log::debug!("stopped `{}`:\nstdout:{}\nstderr:{}", VALVE_FAN_SERVICE, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_path<P: AsRef<std::path::Path>>(home: P) -> std::path::PathBuf {
|
||||
home.as_ref().join(".config/fantastic/fantastic.json")
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
use sysfuss::{SysPath, capability::attributes, SysEntityAttributesExt};
|
||||
use sysfuss::{BasicEntityPath, HwMonPath, HwMonAttribute, HwMonAttributeType, HwMonAttributeItem};
|
||||
|
||||
const HWMON_INDEX: u64 = 5;
|
||||
|
||||
pub const RECALCULATE_ATTR: HwMonAttribute = HwMonAttribute::custom("recalculate");
|
||||
pub const FAN1_INPUT_ATTR: HwMonAttribute = HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Input);
|
||||
pub const FAN1_LABEL_ATTR: HwMonAttribute = HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Label);
|
||||
pub const FAN1_TARGET_ATTR: HwMonAttribute = HwMonAttribute::custom("fan1_target");
|
||||
|
||||
const HWMON_NEEDS: [HwMonAttribute; 3] = [
|
||||
//RECALCULATE_ATTR,
|
||||
FAN1_INPUT_ATTR,
|
||||
FAN1_TARGET_ATTR,
|
||||
FAN1_LABEL_ATTR,
|
||||
];
|
||||
|
||||
pub fn read_fan(hwmon: &HwMonPath) -> Option<u64> {
|
||||
match hwmon.attribute(FAN1_INPUT_ATTR){
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::error!("Failed read_fan(): {}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_thermal_zone(entity: &BasicEntityPath) -> Option<u64> {
|
||||
match entity.attribute("temp".to_owned()) {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::error!("Failed read_thermal_zone(): {}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_fan_recalc(hwmon: &HwMonPath, enabled: bool) -> Result<(), std::io::Error> {
|
||||
hwmon.set(RECALCULATE_ATTR, enabled as u8)
|
||||
//write_single(format!("/sys/class/hwmon/hwmon{}/recalculate", HWMON_INDEX), enabled as u8)
|
||||
}
|
||||
|
||||
pub fn write_fan_target(hwmon: &HwMonPath, rpm: u64) -> Result<(), std::io::Error> {
|
||||
hwmon.set(FAN1_TARGET_ATTR, rpm)
|
||||
//write_single(format!("/sys/class/hwmon/hwmon{}/fan1_target", HWMON_INDEX), rpm)
|
||||
}
|
||||
|
||||
pub fn find_hwmon<P: AsRef<std::path::Path>>(path: P) -> HwMonPath {
|
||||
let syspath = SysPath::path(path);
|
||||
match syspath.hwmon(attributes(HWMON_NEEDS.into_iter()))
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs hwmon iter error: {}", e);
|
||||
syspath.hwmon_by_index(HWMON_INDEX)
|
||||
},
|
||||
Ok(mut iter) => {
|
||||
let entity = iter.next()
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("sysfs hwmon iter empty: [no capable results]");
|
||||
syspath.hwmon_by_index(HWMON_INDEX)
|
||||
});
|
||||
log::info!("Found fan hwmon {}", entity.as_ref().display());
|
||||
entity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_thermal_zone<P: AsRef<std::path::Path>>(path: P) -> BasicEntityPath {
|
||||
let syspath = SysPath::path(path);
|
||||
match syspath.class("thermal",
|
||||
|ent: &BasicEntityPath| ent.exists(&"temp".to_owned()) && ent.exists(&"type".to_owned()) && ent.attribute("type".to_owned()).map(|val: String| val.to_lowercase() != "processor").unwrap_or(false))
|
||||
{
|
||||
Err(e) => {
|
||||
log::error!("sysfs thermal class iter error: {}", e);
|
||||
BasicEntityPath::new("/sys/class/thermal/thermal_zone0")
|
||||
},
|
||||
Ok(mut iter) => {
|
||||
let entity = iter.next()
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("sysfs thermal class iter empty: [no capable results]");
|
||||
BasicEntityPath::new("/sys/class/thermal/thermal_zone0")
|
||||
});
|
||||
log::info!("Found thermal zone {}", entity.as_ref().display());
|
||||
entity
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Fantastic",
|
||||
"version": "0.5.1",
|
||||
"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",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
lockfileVersion: '6.0'
|
||||
lockfileVersion: '6.1'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
|
|
32
src/rust/Cargo.lock
generated
32
src/rust/Cargo.lock
generated
|
@ -17,17 +17,6 @@ version = "1.0.75"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -429,11 +418,8 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
|||
|
||||
[[package]]
|
||||
name = "nrpc"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb8807391415c7f5673c2431e723597bbf6a5c2f510aab30dc8d48d0c76f5d26"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures",
|
||||
"prost",
|
||||
|
@ -441,9 +427,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nrpc-build"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6d47eea1ccd03b060da4b19bec6ac8addcd0129c03b15905a158cd20389d6a4"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"nrpc",
|
||||
"prettyplease 0.2.12",
|
||||
|
@ -807,9 +791,7 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
|||
|
||||
[[package]]
|
||||
name = "usdpl-build"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b732dfafa3876eb2acc3ff17ffc8ed370662e68de3bdf541fbbddaa71ebf52be"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"nrpc-build",
|
||||
"prettyplease 0.2.12",
|
||||
|
@ -822,18 +804,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "usdpl-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad1ff724f8214657706c4c877181d68d65a4db1c782e0a08a74402fe7c480f5"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usdpl-front"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e7ab9901d3db914e70594d94b2bd05c6c2c9d528937c8f466be581e0b79e8f"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"futures",
|
||||
|
|
|
@ -13,12 +13,12 @@ readme = "../../README.md"
|
|||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
usdpl-front = { version = "0.11" }
|
||||
nrpc = { version = "0.10", default-features = false }
|
||||
usdpl-front = { version = "1.0", path = "../../../usdpl-rs/usdpl-front" }
|
||||
nrpc = { version = "1.0", path = "../../../nRPC/nrpc", default-features = false }
|
||||
prost = "0.11"
|
||||
|
||||
[build-dependencies]
|
||||
usdpl-build = { version = "0.11" }
|
||||
usdpl-build = { version = "1.0", path = "../../../usdpl-rs/usdpl-build" }
|
||||
|
||||
[features]
|
||||
debug = ["usdpl-front/debug"]
|
||||
|
|
|
@ -2,12 +2,12 @@ fn main() {
|
|||
println!("CWD: {}", std::env::current_dir().unwrap().display());
|
||||
usdpl_build::front::build(
|
||||
[format!(
|
||||
"{}/../../backend/protos/fantastic.proto",
|
||||
"{}/../../backend-rs/protos/fantastic.proto",
|
||||
std::env::current_dir().unwrap().display()
|
||||
)]
|
||||
.into_iter(),
|
||||
[format!(
|
||||
"{}/../../backend/protos/",
|
||||
"{}/../../backend-rs/protos/",
|
||||
std::env::current_dir().unwrap().display()
|
||||
)]
|
||||
.into_iter(),
|
||||
|
|
Loading…
Reference in a new issue