diff --git a/Cargo.toml b/Cargo.toml index db17a518..4c21d607 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ sg = { git = "https://github.com/flukejones/sg-rs.git" } [profile.release] # thin = 57s, asusd = 9.0M # fat = 72s, asusd = 6.4M -lto = "thin" +lto = "fat" debug = false opt-level = 3 panic = "abort" diff --git a/asusctl/src/cli_opts.rs b/asusctl/src/cli_opts.rs index 7d28dabe..7b00c81e 100644 --- a/asusctl/src/cli_opts.rs +++ b/asusctl/src/cli_opts.rs @@ -54,6 +54,8 @@ pub enum CliCommand { driver, some of the settings will be the same as the older platform interface" )] Armoury(ArmouryCommand), + #[options(name = "backlight", help = "Set screen backlight levels")] + Backlight(BacklightCommand), } #[derive(Debug, Clone, Options)] @@ -102,3 +104,21 @@ pub struct ArmouryCommand { )] pub free: Vec, } + +#[derive(Options)] +pub struct BacklightCommand { + #[options(help = "print help message")] + pub help: bool, + #[options(meta = "", help = "Set screen brightness <0-100>")] + pub screenpad_brightness: Option, + #[options( + meta = "", + help = "Set screenpad gamma brightness 0.5 - 2.2, 1.0 == linear" + )] + pub screenpad_gamma: Option, + #[options( + meta = "", + help = "Set screenpad brightness to sync with primary display" + )] + pub sync_screenpad_brightness: Option, +} diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index 8aad40bc..aa20d50f 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -19,6 +19,7 @@ use rog_dbus::list_iface_blocking; use rog_dbus::scsi_aura::ScsiAuraProxyBlocking; use rog_dbus::zbus_anime::AnimeProxyBlocking; use rog_dbus::zbus_aura::AuraProxyBlocking; +use rog_dbus::zbus_backlight::BacklightProxyBlocking; use rog_dbus::zbus_fan_curves::FanCurvesProxyBlocking; use rog_dbus::zbus_platform::PlatformProxyBlocking; use rog_dbus::zbus_slash::SlashProxyBlocking; @@ -218,6 +219,7 @@ fn do_parsed( Some(CliCommand::Slash(cmd)) => handle_slash(cmd)?, Some(CliCommand::Scsi(cmd)) => handle_scsi(cmd)?, Some(CliCommand::Armoury(cmd)) => handle_armoury_command(cmd)?, + Some(CliCommand::Backlight(cmd)) => handle_backlight(cmd)?, None => { if (!parsed.show_supported && parsed.kbd_bright.is_none() @@ -381,6 +383,46 @@ fn do_gfx() { println!("This command will be removed in future"); } +fn handle_backlight(cmd: &BacklightCommand) -> Result<(), Box> { + if (cmd.screenpad_brightness.is_none() + && cmd.screenpad_gamma.is_none() + && cmd.sync_screenpad_brightness.is_none()) + || cmd.help + { + println!("Missing arg or command\n\n{}", cmd.self_usage()); + + let backlights = find_iface::("xyz.ljones.Backlight")?; + for backlight in backlights { + println!("Current screenpad settings:"); + println!(" Brightness: {}", backlight.screenpad_brightness()?); + println!(" Gamma: {}", backlight.screenpad_gamma()?); + println!( + " Sync with primary: {}", + backlight.screenpad_sync_with_primary()? + ); + } + + return Ok(()); + } + + let backlights = find_iface::("xyz.ljones.Backlight")?; + for backlight in backlights { + if let Some(brightness) = cmd.screenpad_brightness { + backlight.set_screenpad_brightness(brightness)?; + } + + if let Some(gamma) = cmd.screenpad_gamma { + backlight.set_screenpad_gamma(gamma.to_string().as_str())?; + } + + if let Some(sync) = cmd.sync_screenpad_brightness { + backlight.set_screenpad_sync_with_primary(sync)?; + } + } + + Ok(()) +} + fn handle_anime(cmd: &AnimeCommand) -> Result<(), Box> { if (cmd.command.is_none() && cmd.enable_display.is_none() diff --git a/asusd/src/ctrl_backlight.rs b/asusd/src/ctrl_backlight.rs new file mode 100644 index 00000000..0c23e1fa --- /dev/null +++ b/asusd/src/ctrl_backlight.rs @@ -0,0 +1,254 @@ +use std::sync::Arc; + +use futures_util::lock::Mutex; +use log::{info, warn}; +use rog_platform::backlight::{Backlight, BacklightType}; +use zbus::fdo::Error as FdoErr; +use zbus::object_server::SignalEmitter; +use zbus::{interface, Connection}; + +use crate::error::RogError; +use crate::ASUS_ZBUS_PATH; + +#[derive(Debug, Clone)] +pub struct CtrlBacklight { + backlights: Vec, + sync_all: Arc>, + gamma: Arc>, +} + +impl CtrlBacklight { + pub fn new() -> Result { + let mut backlights = Vec::new(); + + if let Ok(primary) = Backlight::new(BacklightType::Primary) { + info!("Found primary display backlight"); + backlights.push(primary); + } + + if let Ok(screenpad) = Backlight::new(BacklightType::Screenpad) { + info!("Found screenpad backlight"); + backlights.push(screenpad); + } + + if backlights.is_empty() { + return Err(RogError::MissingFunction("No backlights found".into())); + } + + Ok(Self { + backlights, + sync_all: Arc::new(Mutex::new(true)), + gamma: Arc::new(Mutex::new(1.0)), + }) + } + + fn get_backlight(&self, device_type: &BacklightType) -> Option<&Backlight> { + self.backlights + .iter() + .find(|b| b.device_type() == device_type) + } + + async fn set_brightness_with_sync( + &self, + device_type: &BacklightType, + level: i32, + ) -> Result<(), FdoErr> { + if let Some(backlight) = self.get_backlight(device_type) { + let max = backlight.get_max_brightness().map_err(|e| { + warn!("Failed to get max brightness: {}", e); + FdoErr::Failed(format!("Failed to get max brightness: {}", e)) + })?; + + let scaled = if *device_type == BacklightType::Screenpad { + // Apply non-linear scaling with the configurable gamma value only for Screenpad + let gamma = *self.gamma.lock().await; + let normalized_level = level as f32 / 100.0; + let gamma_corrected = normalized_level.powf(gamma); + (gamma_corrected * max as f32) as i32 + } else { + // Linear scaling for other devices + level * max / 100 + }; + + backlight.set_brightness(scaled).map_err(|e| { + warn!("Failed to set brightness: {}", e); + FdoErr::Failed(format!("Failed to set brightness: {}", e)) + })?; + + if *self.sync_all.lock().await { + for other in self + .backlights + .iter() + .filter(|b| b.device_type() != device_type) + { + if let Ok(other_max) = other.get_max_brightness() { + let other_scaled = if other.device_type() == &BacklightType::Screenpad { + // Apply gamma only to Screenpad + let gamma = *self.gamma.lock().await; + let normalized_level = level as f32 / 100.0; + let gamma_corrected = normalized_level.powf(gamma); + (gamma_corrected * other_max as f32) as i32 + } else { + // Linear scaling for other devices + level * other_max / 100 + }; + let _ = other.set_brightness(other_scaled); + } + } + } + + Ok(()) + } else { + Err(FdoErr::NotSupported(format!( + "Backlight {:?} not found", + device_type + ))) + } + } + + fn get_brightness_percent(&self, device_type: &BacklightType) -> Result { + if let Some(backlight) = self.get_backlight(device_type) { + let brightness = backlight.get_brightness().map_err(|e| { + warn!("Failed to get brightness: {}", e); + FdoErr::Failed(format!("Failed to get brightness: {}", e)) + })?; + + let max = backlight.get_max_brightness().map_err(|e| { + warn!("Failed to get max brightness: {}", e); + FdoErr::Failed(format!("Failed to get max brightness: {}", e)) + })?; + + Ok((brightness as u32 * 100 / max as u32) as i32) + } else { + Err(FdoErr::NotSupported(format!( + "Backlight {:?} not found", + device_type + ))) + } + } +} + +#[interface(name = "xyz.ljones.Backlight")] +impl CtrlBacklight { + #[zbus(property)] + async fn screenpad_sync_with_primary(&self) -> bool { + *self.sync_all.lock().await + } + + #[zbus(property)] + async fn set_screenpad_sync_with_primary(&self, sync: bool) -> Result<(), zbus::Error> { + *self.sync_all.lock().await = sync; + Ok(()) + } + + #[zbus(property)] + async fn screenpad_gamma(&self) -> String { + (*self.gamma.lock().await).to_string() + } + + #[zbus(property)] + async fn set_screenpad_gamma(&self, value: &str) -> Result<(), zbus::Error> { + let gamma: f32 = value + .parse() + .map_err(|_| FdoErr::Failed("Invalid gamma value, must be a valid number".into()))?; + + if gamma < 0.1 { + return Err(FdoErr::Failed("Gamma value must be greater than 0".into()).into()); + } + if gamma > 2.0 { + return Err(FdoErr::Failed("Gamma value must be 2.0 or less".into()).into()); + } + *self.gamma.lock().await = gamma; + Ok(()) + } + + #[zbus(property)] + async fn primary_brightness(&self) -> Result { + self.get_brightness_percent(&BacklightType::Primary) + } + + #[zbus(property)] + async fn set_primary_brightness( + &self, + #[zbus(signal_context)] ctxt: SignalEmitter<'_>, + level: i32, + ) -> Result<(), zbus::Error> { + if level > 100 { + return Err(FdoErr::Failed("Brightness level must be 0-100".into()).into()); + } + + self.set_brightness_with_sync(&BacklightType::Primary, level) + .await?; + self.primary_brightness_changed(&ctxt).await?; + + Ok(()) + } + + #[zbus(property)] + async fn screenpad_brightness(&self) -> Result { + self.get_brightness_percent(&BacklightType::Screenpad) + } + + #[zbus(property)] + async fn set_screenpad_brightness( + &self, + #[zbus(signal_context)] ctxt: SignalEmitter<'_>, + level: i32, + ) -> Result<(), zbus::Error> { + if level > 100 { + return Err(FdoErr::Failed("Brightness level must be 0-100".into()).into()); + } + + self.set_brightness_with_sync(&BacklightType::Screenpad, level) + .await?; + self.screenpad_brightness_changed(&ctxt).await?; + + Ok(()) + } + + #[zbus(property)] + async fn screenpad_power(&self) -> Result { + if let Some(backlight) = self.get_backlight(&BacklightType::Screenpad) { + let power = backlight.get_bl_power().map_err(|e| { + warn!("Failed to get backlight power: {}", e); + FdoErr::Failed(format!("Failed to get backlight power: {}", e)) + })?; + Ok(power == 0) + } else { + Err(FdoErr::NotSupported("Screenpad backlight not found".into())) + } + } + + #[zbus(property)] + async fn set_screenpad_power( + &self, + #[zbus(signal_context)] ctxt: SignalEmitter<'_>, + power: bool, + ) -> Result<(), zbus::Error> { + if let Some(backlight) = self.get_backlight(&BacklightType::Screenpad) { + backlight + .set_bl_power(if power { 0 } else { 1 }) + .map_err(|e| { + warn!("Failed to set backlight power: {}", e); + FdoErr::Failed(format!("Failed to set backlight power: {}", e)) + })?; + self.screenpad_power_changed(&ctxt).await?; + Ok(()) + } else { + Err(FdoErr::NotSupported("Screenpad backlight not found".into()).into()) + } + } +} + +impl crate::ZbusRun for CtrlBacklight { + async fn add_to_server(self, server: &mut Connection) { + Self::add_to_server_helper(self, ASUS_ZBUS_PATH, server).await; + } +} + +impl crate::Reloadable for CtrlBacklight { + async fn reload(&mut self) -> Result<(), RogError> { + info!("Reloading backlight settings"); + Ok(()) + } +} diff --git a/asusd/src/daemon.rs b/asusd/src/daemon.rs index 32bfba3c..544b86e5 100644 --- a/asusd/src/daemon.rs +++ b/asusd/src/daemon.rs @@ -6,9 +6,10 @@ use ::zbus::Connection; use asusd::asus_armoury::start_attributes_zbus; use asusd::aura_manager::DeviceManager; use asusd::config::Config; +use asusd::ctrl_backlight::CtrlBacklight; use asusd::ctrl_fancurves::CtrlFanCurveZbus; use asusd::ctrl_platform::CtrlPlatform; -use asusd::{print_board_info, start_tasks, CtrlTask, DBUS_NAME}; +use asusd::{print_board_info, start_tasks, CtrlTask, ZbusRun, DBUS_NAME}; use config_traits::{StdConfig, StdConfigLoad1}; use futures_util::lock::Mutex; use log::{error, info}; @@ -90,6 +91,15 @@ async fn start_daemon() -> Result<(), Box> { } } + match CtrlBacklight::new() { + Ok(backlight) => { + backlight.add_to_server(&mut server).await; + } + Err(err) => { + error!("Backlight: {}", err); + } + } + match CtrlPlatform::new( platform, power, diff --git a/asusd/src/lib.rs b/asusd/src/lib.rs index fc1c2318..9dd36854 100644 --- a/asusd/src/lib.rs +++ b/asusd/src/lib.rs @@ -1,6 +1,7 @@ #![deny(unused_must_use)] /// Configuration loading, saving pub mod config; +pub mod ctrl_backlight; /// Control platform profiles + fan-curves if available pub mod ctrl_fancurves; /// Control ASUS bios function such as boot sound, Optimus/Dedicated gfx mode diff --git a/rog-dbus/src/lib.rs b/rog-dbus/src/lib.rs index 5b6527bf..e549e249 100644 --- a/rog-dbus/src/lib.rs +++ b/rog-dbus/src/lib.rs @@ -5,6 +5,7 @@ pub mod asus_armoury; pub mod scsi_aura; pub mod zbus_anime; pub mod zbus_aura; +pub mod zbus_backlight; pub mod zbus_fan_curves; pub mod zbus_platform; pub mod zbus_slash; diff --git a/rog-dbus/src/zbus_backlight.rs b/rog-dbus/src/zbus_backlight.rs new file mode 100644 index 00000000..9e9bb73d --- /dev/null +++ b/rog-dbus/src/zbus_backlight.rs @@ -0,0 +1,59 @@ +//! # D-Bus interface proxy for: `xyz.ljones.Backlight` +//! +//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection +//! data. Source: `Interface '/xyz/ljones' from service 'xyz.ljones.Asusd' on +//! system bus`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the [Writing a client proxy] section of the +//! zbus documentation. +//! +//! This type implements the [D-Bus standard interfaces], +//! (`org.freedesktop.DBus.*`) for which the following zbus API can be used: +//! +//! * [`zbus::fdo::PeerProxy`] +//! * [`zbus::fdo::PropertiesProxy`] +//! * [`zbus::fdo::IntrospectableProxy`] +//! +//! Consequently `zbus-xmlgen` did not generate code for the above interfaces. +//! +//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html +//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces, +use zbus::proxy; +#[proxy( + interface = "xyz.ljones.Backlight", + default_service = "xyz.ljones.Asusd", + default_path = "/xyz/ljones" +)] +pub trait Backlight { + /// PrimaryBrightness property + #[zbus(property)] + fn primary_brightness(&self) -> zbus::Result; + #[zbus(property)] + fn set_primary_brightness(&self, value: i32) -> zbus::Result<()>; + + /// ScreenpadBrightness property + #[zbus(property)] + fn screenpad_brightness(&self) -> zbus::Result; + #[zbus(property)] + fn set_screenpad_brightness(&self, value: i32) -> zbus::Result<()>; + + /// ScreenpadGamma property + #[zbus(property)] + fn screenpad_gamma(&self) -> zbus::Result; + #[zbus(property)] + fn set_screenpad_gamma(&self, value: &str) -> zbus::Result<()>; + + /// ScreenpadPower property + #[zbus(property)] + fn screenpad_power(&self) -> zbus::Result; + #[zbus(property)] + fn set_screenpad_power(&self, value: bool) -> zbus::Result<()>; + + /// ScreenpadSyncWithPrimary property + #[zbus(property)] + fn screenpad_sync_with_primary(&self) -> zbus::Result; + #[zbus(property)] + fn set_screenpad_sync_with_primary(&self, value: bool) -> zbus::Result<()>; +} diff --git a/rog-platform/src/backlight.rs b/rog-platform/src/backlight.rs new file mode 100644 index 00000000..823ef842 --- /dev/null +++ b/rog-platform/src/backlight.rs @@ -0,0 +1,75 @@ +use std::path::PathBuf; + +use log::{info, warn}; + +use crate::error::{PlatformError, Result}; +use crate::{attr_num, to_device}; + +/// The "backlight" device provides access to screen brightness control +#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)] +pub struct Backlight { + path: PathBuf, + device_type: BacklightType, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)] +pub enum BacklightType { + Primary, + Screenpad, +} + +impl Backlight { + attr_num!("brightness", path, i32); + + attr_num!("max_brightness", path, i32); + + attr_num!("bl_power", path, i32); + + pub fn new(device_type: BacklightType) -> Result { + let mut enumerator = udev::Enumerator::new().map_err(|err| { + warn!("{}", err); + PlatformError::Udev("enumerator failed".into(), err) + })?; + enumerator.match_subsystem("backlight").map_err(|err| { + warn!("{}", err); + PlatformError::Udev("match_subsystem failed".into(), err) + })?; + + for device in enumerator.scan_devices().map_err(|err| { + warn!("{}", err); + PlatformError::Udev("scan_devices failed".into(), err) + })? { + info!("Backlight: Checking {:?}", device.syspath()); + match device_type { + BacklightType::Primary => { + if device.sysname().to_string_lossy() == "intel_backlight" { + info!("Found primary backlight at {:?}", device.sysname()); + return Ok(Self { + path: device.syspath().to_path_buf(), + device_type, + }); + } + } + BacklightType::Screenpad => { + let name = device.sysname().to_string_lossy(); + if name == "asus_screenpad" || name == "asus_screenpad_backlight" { + info!("Found screenpad backlight at {:?}", device.sysname()); + return Ok(Self { + path: device.syspath().to_path_buf(), + device_type, + }); + } + } + } + } + + Err(PlatformError::MissingFunction(format!( + "Backlight {:?} not found", + device_type + ))) + } + + pub fn device_type(&self) -> &BacklightType { + &self.device_type + } +} diff --git a/rog-platform/src/keyboard_led.rs b/rog-platform/src/keyboard_led.rs index 183e1db6..319905a0 100644 --- a/rog-platform/src/keyboard_led.rs +++ b/rog-platform/src/keyboard_led.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use log::{info, warn}; use crate::error::{PlatformError, Result}; -use crate::{attr_u8, has_attr, set_attr_u8_array, to_device}; +use crate::{attr_num, has_attr, set_attr_u8_array, to_device}; /// The sysfs control for backlight levels. This is only for the 3-step /// backlight setting, and for TUF laptops. It is not a hard requirement @@ -14,7 +14,7 @@ pub struct KeyboardBacklight { } impl KeyboardBacklight { - attr_u8!("brightness", path); + attr_num!("brightness", path, u8); has_attr!("kbd_rgb_mode" path); diff --git a/rog-platform/src/lib.rs b/rog-platform/src/lib.rs index deb4fd12..f8681dbe 100644 --- a/rog-platform/src/lib.rs +++ b/rog-platform/src/lib.rs @@ -2,6 +2,7 @@ //! on ROG, Strix, and TUF laptops. pub mod asus_armoury; +pub mod backlight; pub mod cpu; pub mod error; pub mod hid_raw; @@ -55,18 +56,29 @@ pub fn write_attr_bool(device: &mut Device, attr: &str, value: bool) -> Result<( }) } -pub fn read_attr_u8(device: &Device, attr_name: &str) -> Result { +pub fn read_attr_num(device: &Device, attr_name: &str) -> Result +where + T: std::str::FromStr, + ::Err: std::fmt::Debug, +{ if let Some(value) = device.attribute_value(attr_name) { let tmp = value.to_string_lossy(); - return tmp.parse::().map_err(|_e| PlatformError::ParseNum); + return tmp.parse::().map_err(|_e| PlatformError::ParseNum); } Err(PlatformError::AttrNotFound(attr_name.to_owned())) } -pub fn write_attr_u8(device: &mut Device, attr: &str, value: u8) -> Result<()> { - device - .set_attribute_value(attr, value.to_string()) - .map_err(|e| PlatformError::IoPath(attr.into(), e)) +pub fn write_attr_num(device: &mut Device, attr_name: &str, value: T) -> Result<()> +where + T: std::fmt::Display, +{ + if device + .set_attribute_value(attr_name, format!("{value}")) + .is_err() + { + return Err(PlatformError::AttrNotFound(attr_name.to_owned())); + } + Ok(()) } pub fn read_attr_u8_array(device: &Device, attr_name: &str) -> Result> { diff --git a/rog-platform/src/macros.rs b/rog-platform/src/macros.rs index 1cbb65c3..554ce005 100644 --- a/rog-platform/src/macros.rs +++ b/rog-platform/src/macros.rs @@ -74,36 +74,41 @@ macro_rules! attr_bool { } #[macro_export] -macro_rules! get_attr_u8 { - ($(#[$attr:meta])* $attr_name:literal $item:ident) => { +macro_rules! get_attr_num { + ($(#[$attr:meta])* $attr_name:literal $item:ident $type:ty) => { concat_idents::concat_idents!(fn_name = get_, $attr_name { $(#[$attr])* - pub fn fn_name(&self) -> Result { - $crate::read_attr_u8(&to_device(&self.$item)?, $attr_name) + pub fn fn_name(&self) -> Result<$type> { + $crate::read_attr_num::<$type>(&to_device(&self.$item)?, $attr_name) } }); }; + ($(#[$attr:meta])* $attr_name:literal $item:ident) => { + $crate::get_attr_num!($(#[$attr])* $attr_name $item $type); + }; } -/// Most attributes expect `u8` as a char, so `1` should be written as `b'1'`. #[macro_export] -macro_rules! set_attr_u8 { - ($(#[$attr:meta])* $attr_name:literal $item:ident) => { +macro_rules! set_attr_num { + ($(#[$attr:meta])* $attr_name:literal $item:ident $type:ty) => { concat_idents::concat_idents!(fn_name = set_, $attr_name { $(#[$attr])* - pub fn fn_name(&self, value: u8) -> Result<()> { - $crate::write_attr_u8(&mut to_device(&self.$item)?, $attr_name, value) + pub fn fn_name(&self, value: $type) -> Result<()> { + $crate::write_attr_num(&mut to_device(&self.$item)?, $attr_name, value as $type) } }); }; + ($(#[$attr:meta])* $attr_name:literal $item:ident) => { + $crate::set_attr_num!($(#[$attr])* $attr_name $item $type); + }; } #[macro_export] -macro_rules! attr_u8 { - ($(#[$attr:meta])* $attr_name:literal, $item:ident) => { +macro_rules! attr_num { + ($(#[$attr:meta])* $attr_name:literal, $item:ident, $type:ty) => { $crate::has_attr!($(#[$attr])* $attr_name $item); - $crate::get_attr_u8!($(#[$attr])* $attr_name $item); - $crate::set_attr_u8!($(#[$attr])* $attr_name $item); + $crate::get_attr_num!($(#[$attr])* $attr_name $item $type); + $crate::set_attr_num!($(#[$attr])* $attr_name $item $type); $crate::watch_attr!($(#[$attr])* $attr_name $item); }; } diff --git a/rog-platform/src/power.rs b/rog-platform/src/power.rs index f4f02d0a..adf75e2c 100644 --- a/rog-platform/src/power.rs +++ b/rog-platform/src/power.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use log::{info, warn}; use crate::error::{PlatformError, Result}; -use crate::{attr_u8, to_device}; +use crate::{attr_num, to_device}; /// The "platform" device provides access to things like: /// - `dgpu_disable` @@ -20,9 +20,9 @@ pub struct AsusPower { } impl AsusPower { - attr_u8!("charge_control_end_threshold", battery); + attr_num!("charge_control_end_threshold", battery, u8); - attr_u8!("online", mains); + attr_num!("online", mains, u8); /// When checking for battery this will look in order: /// - if attr `manufacturer` contains `asus`