mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Compare commits
6 Commits
devel
...
38e2d99946
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38e2d99946 | ||
|
|
f471f340d4 | ||
|
|
8095ac34ed | ||
|
|
9d629b62ca | ||
|
|
5282c56f59 | ||
|
|
0d2cd4eb10 |
@@ -1,10 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
- Fix PPT sliders not updating
|
|
||||||
|
|
||||||
## [6.3.2]
|
## [6.3.2]
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -970,72 +970,52 @@ fn print_firmware_attr(attr: &AsusArmouryProxyBlocking) -> Result<(), Box<dyn st
|
|||||||
let name = attr.name()?;
|
let name = attr.name()?;
|
||||||
println!("{}:", <&str>::from(name));
|
println!("{}:", <&str>::from(name));
|
||||||
|
|
||||||
// Be resilient to DBus read failures: if any read fails, show "unavailable"
|
let attrs = attr.available_attrs()?;
|
||||||
let attrs = attr.available_attrs().unwrap_or_default();
|
if attrs.contains(&"min_value".to_string())
|
||||||
|
&& attrs.contains(&"max_value".to_string())
|
||||||
let has_min = attrs.contains(&"min_value".to_string());
|
&& attrs.contains(&"current_value".to_string())
|
||||||
let has_max = attrs.contains(&"max_value".to_string());
|
{
|
||||||
let has_current = attrs.contains(&"current_value".to_string());
|
let c = attr.current_value()?;
|
||||||
let has_possible = attrs.contains(&"possible_values".to_string());
|
let min = attr.min_value()?;
|
||||||
let has_default = attrs.contains(&"default_value".to_string());
|
let max = attr.max_value()?;
|
||||||
|
println!(" current: {min}..[{c}]..{max}");
|
||||||
if has_min && has_max && has_current {
|
if attrs.contains(&"default_value".to_string()) {
|
||||||
let c = attr.current_value().ok();
|
println!(" default: {}\n", attr.default_value()?);
|
||||||
let min = attr.min_value().ok();
|
|
||||||
let max = attr.max_value().ok();
|
|
||||||
match (min, c, max) {
|
|
||||||
(Some(min), Some(c), Some(max)) => println!(" current: {min}..[{c}]..{max}"),
|
|
||||||
_ => println!(" current: unavailable"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_default {
|
|
||||||
match attr.default_value().ok() {
|
|
||||||
Some(d) => println!(" default: {}\n", d),
|
|
||||||
None => println!(" default: unavailable\n"),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
} else if has_possible && has_current {
|
} else if attrs.contains(&"possible_values".to_string())
|
||||||
let c = attr.current_value().ok();
|
&& attrs.contains(&"current_value".to_string())
|
||||||
let v = attr.possible_values().ok();
|
{
|
||||||
if let (Some(c), Some(v)) = (c, v) {
|
let c = attr.current_value()?;
|
||||||
for p in v.iter().enumerate() {
|
let v = attr.possible_values()?;
|
||||||
if p.0 == 0 {
|
for p in v.iter().enumerate() {
|
||||||
print!(" current: [");
|
if p.0 == 0 {
|
||||||
}
|
print!(" current: [");
|
||||||
if *p.1 == c {
|
|
||||||
print!("({c})");
|
|
||||||
} else {
|
|
||||||
print!("{}", p.1);
|
|
||||||
}
|
|
||||||
if p.0 < v.len() - 1 {
|
|
||||||
print!(",");
|
|
||||||
}
|
|
||||||
if p.0 == v.len() - 1 {
|
|
||||||
print!("]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if has_default {
|
if *p.1 == c {
|
||||||
match attr.default_value().ok() {
|
print!("({c})");
|
||||||
Some(d) => println!(" default: {}\n", d),
|
|
||||||
None => println!(" default: unavailable\n"),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println!("\n");
|
print!("{}", p.1);
|
||||||
}
|
}
|
||||||
|
if p.0 < v.len() - 1 {
|
||||||
|
print!(",");
|
||||||
|
}
|
||||||
|
if p.0 == v.len() - 1 {
|
||||||
|
print!("]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if attrs.contains(&"default_value".to_string()) {
|
||||||
|
println!(" default: {}\n", attr.default_value()?);
|
||||||
} else {
|
} else {
|
||||||
println!(" current: unavailable\n");
|
println!("\n");
|
||||||
}
|
|
||||||
} else if has_current {
|
|
||||||
match attr.current_value().ok() {
|
|
||||||
Some(c) => println!(" current: {c}\n"),
|
|
||||||
None => println!(" current: unavailable\n"),
|
|
||||||
}
|
}
|
||||||
|
} else if attrs.contains(&"current_value".to_string()) {
|
||||||
|
let c = attr.current_value()?;
|
||||||
|
println!(" current: {c}\n");
|
||||||
} else {
|
} else {
|
||||||
println!(" unavailable\n");
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,30 @@ pub struct Aura {
|
|||||||
impl Aura {
|
impl Aura {
|
||||||
/// Initialise the device if required.
|
/// Initialise the device if required.
|
||||||
pub async fn do_initialization(&self) -> Result<(), RogError> {
|
pub async fn do_initialization(&self) -> Result<(), RogError> {
|
||||||
|
if let Some(hid) = &self.hid {
|
||||||
|
let hid = hid.lock().await;
|
||||||
|
let init_1: [u8; 2] = [
|
||||||
|
0x5d, 0xb9,
|
||||||
|
];
|
||||||
|
let init_2 = b"]ASUS Tech.Inc.";
|
||||||
|
let init_3: [u8; 6] = [
|
||||||
|
0x5d, 0x05, 0x20, 0x31, 0, 0x1a,
|
||||||
|
];
|
||||||
|
|
||||||
|
hid.write_bytes(&init_1)?;
|
||||||
|
hid.write_bytes(init_2)?;
|
||||||
|
hid.write_bytes(&init_3)?;
|
||||||
|
|
||||||
|
let config = self.config.lock().await;
|
||||||
|
if config.support_data.device_name.contains("GZ30")
|
||||||
|
|| config.support_data.device_name.contains("Z13")
|
||||||
|
{
|
||||||
|
let z13_init: [u8; 4] = [
|
||||||
|
0x5d, 0xc0, 0x03, 0x01,
|
||||||
|
];
|
||||||
|
hid.write_bytes(&z13_init)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,9 +176,54 @@ impl Aura {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = config.enabled.to_bytes(config.led_type);
|
let mut enabled = config.enabled.clone();
|
||||||
|
if config.support_data.device_name.contains("GZ30")
|
||||||
|
|| config.support_data.device_name.contains("Z13")
|
||||||
|
{
|
||||||
|
let logo_state = enabled
|
||||||
|
.states
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.zone == PowerZones::Logo)
|
||||||
|
.cloned();
|
||||||
|
if let Some(logo) = logo_state {
|
||||||
|
let mut lid_found = false;
|
||||||
|
let mut bar_found = false;
|
||||||
|
|
||||||
|
for s in enabled.states.iter_mut() {
|
||||||
|
if s.zone == PowerZones::Lid {
|
||||||
|
s.boot = logo.boot;
|
||||||
|
s.awake = logo.awake;
|
||||||
|
s.sleep = logo.sleep;
|
||||||
|
s.shutdown = logo.shutdown;
|
||||||
|
lid_found = true;
|
||||||
|
}
|
||||||
|
if s.zone == PowerZones::Lightbar {
|
||||||
|
s.boot = logo.boot;
|
||||||
|
s.awake = logo.awake;
|
||||||
|
s.sleep = logo.sleep;
|
||||||
|
s.shutdown = logo.shutdown;
|
||||||
|
bar_found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lid_found {
|
||||||
|
let mut new_state = logo;
|
||||||
|
new_state.zone = PowerZones::Lid;
|
||||||
|
enabled.states.push(new_state.clone());
|
||||||
|
new_state.zone = PowerZones::Lightbar;
|
||||||
|
enabled.states.push(new_state);
|
||||||
|
} else if !bar_found {
|
||||||
|
// Lid found but not bar?
|
||||||
|
let mut new_state = logo;
|
||||||
|
new_state.zone = PowerZones::Lightbar;
|
||||||
|
enabled.states.push(new_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = enabled.to_bytes(config.led_type);
|
||||||
let msg = [
|
let msg = [
|
||||||
0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3],
|
0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3], 0xff,
|
||||||
];
|
];
|
||||||
hid_raw.write_bytes(&msg)?;
|
hid_raw.write_bytes(&msg)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -654,15 +654,6 @@ impl CtrlPlatform {
|
|||||||
.enabled = enable;
|
.enabled = enable;
|
||||||
self.config.lock().await.write();
|
self.config.lock().await.write();
|
||||||
|
|
||||||
// Re-emit armoury attribute limits so GUI sees updated min/max for PPT
|
|
||||||
// attributes which can change when enabling/disabling PPT tuning groups.
|
|
||||||
// Fire-and-forget: we don't want to fail the property call if emit fails.
|
|
||||||
let _ = self
|
|
||||||
.armoury_registry
|
|
||||||
.emit_limits(&self.connection)
|
|
||||||
.await
|
|
||||||
.map_err(|e| log::error!("Failed to emit armoury limits: {e:?}"));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -721,13 +712,6 @@ impl ReloadAndNotify for CtrlPlatform {
|
|||||||
*config = data;
|
*config = data;
|
||||||
config.base_charge_control_end_threshold =
|
config.base_charge_control_end_threshold =
|
||||||
base_charge_control_end_threshold.unwrap_or_default();
|
base_charge_control_end_threshold.unwrap_or_default();
|
||||||
|
|
||||||
// Ensure any armoury limits changes from the new config are emitted
|
|
||||||
let _ = self
|
|
||||||
.armoury_registry
|
|
||||||
.emit_limits(&self.connection)
|
|
||||||
.await
|
|
||||||
.map_err(|e| log::error!("Failed to emit armoury limits after reload: {e:?}"));
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -980,6 +980,24 @@
|
|||||||
advanced_type: r#None,
|
advanced_type: r#None,
|
||||||
power_zones: [Keyboard],
|
power_zones: [Keyboard],
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
device_name: "GZ302",
|
||||||
|
product_id: "18c6",
|
||||||
|
layout_name: "",
|
||||||
|
basic_modes: [Static, Breathe, Pulse],
|
||||||
|
basic_zones: [],
|
||||||
|
advanced_type: r#None,
|
||||||
|
power_zones: [Logo],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
device_name: "GZ302",
|
||||||
|
product_id: "1a30",
|
||||||
|
layout_name: "ga401q",
|
||||||
|
basic_modes: [Static, Breathe, Pulse],
|
||||||
|
basic_zones: [],
|
||||||
|
advanced_type: r#None,
|
||||||
|
power_zones: [Keyboard],
|
||||||
|
),
|
||||||
(
|
(
|
||||||
device_name: "RC71L",
|
device_name: "RC71L",
|
||||||
product_id: "",
|
product_id: "",
|
||||||
|
|||||||
@@ -123,7 +123,8 @@ impl AuraPowerState {
|
|||||||
| ((self.shutdown as u32) << 7)
|
| ((self.shutdown as u32) << 7)
|
||||||
}
|
}
|
||||||
PowerZones::Lightbar => {
|
PowerZones::Lightbar => {
|
||||||
((self.boot as u32) << (7 + 2))
|
((self.awake as u32) << (7 + 1))
|
||||||
|
| ((self.boot as u32) << (7 + 2))
|
||||||
| ((self.awake as u32) << (7 + 3))
|
| ((self.awake as u32) << (7 + 3))
|
||||||
| ((self.sleep as u32) << (7 + 4))
|
| ((self.sleep as u32) << (7 + 4))
|
||||||
| ((self.shutdown as u32) << (7 + 5))
|
| ((self.shutdown as u32) << (7 + 5))
|
||||||
@@ -133,12 +134,20 @@ impl AuraPowerState {
|
|||||||
| ((self.awake as u32) << (15 + 2))
|
| ((self.awake as u32) << (15 + 2))
|
||||||
| ((self.sleep as u32) << (15 + 3))
|
| ((self.sleep as u32) << (15 + 3))
|
||||||
| ((self.shutdown as u32) << (15 + 4))
|
| ((self.shutdown as u32) << (15 + 4))
|
||||||
|
| ((self.boot as u32) << (15 + 5))
|
||||||
|
| ((self.awake as u32) << (15 + 6))
|
||||||
|
| ((self.sleep as u32) << (15 + 7))
|
||||||
|
| ((self.shutdown as u32) << (15 + 8))
|
||||||
}
|
}
|
||||||
PowerZones::RearGlow => {
|
PowerZones::RearGlow => {
|
||||||
((self.boot as u32) << (23 + 1))
|
((self.boot as u32) << (23 + 1))
|
||||||
| ((self.awake as u32) << (23 + 2))
|
| ((self.awake as u32) << (23 + 2))
|
||||||
| ((self.sleep as u32) << (23 + 3))
|
| ((self.sleep as u32) << (23 + 3))
|
||||||
| ((self.shutdown as u32) << (23 + 4))
|
| ((self.shutdown as u32) << (23 + 4))
|
||||||
|
| ((self.boot as u32) << (23 + 5))
|
||||||
|
| ((self.awake as u32) << (23 + 6))
|
||||||
|
| ((self.sleep as u32) << (23 + 7))
|
||||||
|
| ((self.shutdown as u32) << (23 + 8))
|
||||||
}
|
}
|
||||||
PowerZones::None | PowerZones::KeyboardAndLightbar => 0,
|
PowerZones::None | PowerZones::KeyboardAndLightbar => 0,
|
||||||
}
|
}
|
||||||
@@ -618,19 +627,19 @@ mod test {
|
|||||||
assert_eq!(shut_keyb_, "10000000, 00000000, 00000000, 00000000");
|
assert_eq!(shut_keyb_, "10000000, 00000000, 00000000, 00000000");
|
||||||
//
|
//
|
||||||
assert_eq!(boot_bar__, "00000000, 00000010, 00000000, 00000000");
|
assert_eq!(boot_bar__, "00000000, 00000010, 00000000, 00000000");
|
||||||
assert_eq!(awake_bar_, "00000000, 00000100, 00000000, 00000000");
|
assert_eq!(awake_bar_, "00000000, 00000101, 00000000, 00000000");
|
||||||
assert_eq!(sleep_bar_, "00000000, 00001000, 00000000, 00000000");
|
assert_eq!(sleep_bar_, "00000000, 00001000, 00000000, 00000000");
|
||||||
assert_eq!(shut_bar__, "00000000, 00010000, 00000000, 00000000");
|
assert_eq!(shut_bar__, "00000000, 00010000, 00000000, 00000000");
|
||||||
//
|
//
|
||||||
assert_eq!(boot_lid__, "00000000, 00000000, 00000001, 00000000");
|
assert_eq!(boot_lid__, "00000000, 00000000, 00010001, 00000000");
|
||||||
assert_eq!(awake_lid_, "00000000, 00000000, 00000010, 00000000");
|
assert_eq!(awake_lid_, "00000000, 00000000, 00100010, 00000000");
|
||||||
assert_eq!(sleep_lid_, "00000000, 00000000, 00000100, 00000000");
|
assert_eq!(sleep_lid_, "00000000, 00000000, 01000100, 00000000");
|
||||||
assert_eq!(shut_lid__, "00000000, 00000000, 00001000, 00000000");
|
assert_eq!(shut_lid__, "00000000, 00000000, 10001000, 00000000");
|
||||||
//
|
//
|
||||||
assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00000001");
|
assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00010001");
|
||||||
assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00000010");
|
assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00100010");
|
||||||
assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 00000100");
|
assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 01000100");
|
||||||
assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 00001000");
|
assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 10001000");
|
||||||
|
|
||||||
// All on
|
// All on
|
||||||
let byte1 = to_binary_string_post2021(&LaptopAuraPower {
|
let byte1 = to_binary_string_post2021(&LaptopAuraPower {
|
||||||
@@ -657,6 +666,6 @@ mod test {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
assert_eq!(byte1, "11111111, 00011110, 00001111, 00001111");
|
assert_eq!(byte1, "11111111, 00011111, 11111111, 11111111");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ impl From<&str> for AuraDeviceType {
|
|||||||
match s.to_lowercase().trim_start_matches("0x") {
|
match s.to_lowercase().trim_start_matches("0x") {
|
||||||
"tuf" => AuraDeviceType::LaptopKeyboardTuf,
|
"tuf" => AuraDeviceType::LaptopKeyboardTuf,
|
||||||
"1932" => AuraDeviceType::ScsiExtDisk,
|
"1932" => AuraDeviceType::ScsiExtDisk,
|
||||||
"1866" | "18c6" | "1869" | "1854" => Self::LaptopKeyboardPre2021,
|
"1866" | "1869" | "1854" => Self::LaptopKeyboardPre2021,
|
||||||
"1abe" | "1b4c" => Self::Ally,
|
"1abe" | "1b4c" => Self::Ally,
|
||||||
"19b3" | "193b" => Self::AnimeOrSlash,
|
"19b3" | "193b" => Self::AnimeOrSlash,
|
||||||
"19b6" => Self::LaptopKeyboard2021,
|
"19b6" | "1a30" | "18c6" => Self::LaptopKeyboard2021,
|
||||||
_ => Self::Unknown,
|
_ => Self::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,6 @@ impl CurveData {
|
|||||||
/// Write this curve to the device fan specified by `self.fan`
|
/// Write this curve to the device fan specified by `self.fan`
|
||||||
pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> {
|
pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> {
|
||||||
let pwm_num: char = self.fan.into();
|
let pwm_num: char = self.fan.into();
|
||||||
let enable = if self.enabled { '1' } else { '2' };
|
|
||||||
|
|
||||||
for (index, out) in self.pwm.iter().enumerate() {
|
for (index, out) in self.pwm.iter().enumerate() {
|
||||||
let pwm = pwm_str(pwm_num, index);
|
let pwm = pwm_str(pwm_num, index);
|
||||||
@@ -176,10 +175,20 @@ impl CurveData {
|
|||||||
device.set_attribute_value(&temp, out.to_string())?;
|
device.set_attribute_value(&temp, out.to_string())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable must be done *after* all points are written pwm3_enable
|
// Note: pwm_enable is set by write_profile_curve_to_platform after all
|
||||||
|
// curves are written, because on some devices (e.g., ASUS Z13 2025)
|
||||||
|
// setting any pwm_enable to 2 resets ALL fan enables.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the enable state for this fan curve
|
||||||
|
pub fn set_enable(&self, device: &mut Device) -> std::io::Result<()> {
|
||||||
|
let pwm_num: char = self.fan.into();
|
||||||
|
let enable = if self.enabled { "1" } else { "2" };
|
||||||
|
let enable_attr = format!("pwm{pwm_num}_enable");
|
||||||
device
|
device
|
||||||
.set_attribute_value(format!("pwm{pwm_num}_enable"), enable.to_string())
|
.set_attribute_value(&enable_attr, enable.to_string())
|
||||||
.map_err(|e| error!("Failed to set pwm{pwm_num}_enable to {enable}: {e:?}"))
|
.map_err(|e| error!("Failed to set {enable_attr} to {enable}: {e:?}"))
|
||||||
.ok();
|
.ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,15 +181,29 @@ impl FanCurveProfiles {
|
|||||||
PlatformProfile::Quiet | PlatformProfile::LowPower => &mut self.quiet,
|
PlatformProfile::Quiet | PlatformProfile::LowPower => &mut self.quiet,
|
||||||
PlatformProfile::Custom => &mut self.custom,
|
PlatformProfile::Custom => &mut self.custom,
|
||||||
};
|
};
|
||||||
for fan in fans.iter().filter(|f| !f.enabled) {
|
|
||||||
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}");
|
// First write all curve data (pwm/temp values) for all fans
|
||||||
|
for fan in fans.iter() {
|
||||||
|
debug!("write_profile_curve_to_platform: writing curve data for profile:{profile}, {fan:?}");
|
||||||
fan.write_to_device(device)?;
|
fan.write_to_device(device)?;
|
||||||
}
|
}
|
||||||
// Write enabled fans last because the kernel currently resets *all* if one is
|
|
||||||
// disabled
|
// Then set enables: disabled fans first, then enabled fans last.
|
||||||
|
// This order is important because on some devices (e.g., ASUS Z13 2025)
|
||||||
|
// setting any pwm_enable to 2 (disabled) resets ALL fan enables.
|
||||||
|
for fan in fans.iter().filter(|f| !f.enabled) {
|
||||||
|
debug!(
|
||||||
|
"write_profile_curve_to_platform: disabling fan for profile:{profile}, {:?}",
|
||||||
|
fan.fan
|
||||||
|
);
|
||||||
|
fan.set_enable(device)?;
|
||||||
|
}
|
||||||
for fan in fans.iter().filter(|f| f.enabled) {
|
for fan in fans.iter().filter(|f| f.enabled) {
|
||||||
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}");
|
debug!(
|
||||||
fan.write_to_device(device)?;
|
"write_profile_curve_to_platform: enabling fan for profile:{profile}, {:?}",
|
||||||
|
fan.fan
|
||||||
|
);
|
||||||
|
fan.set_enable(device)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user