Rearchitect

This commit is contained in:
Luke D Jones
2020-08-03 12:25:39 +12:00
parent 16681d03dd
commit b0a267b412
9 changed files with 235 additions and 215 deletions

View File

@@ -0,0 +1,69 @@
use crate::config::Config;
use log::{error, info, warn};
use std::error::Error;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
pub struct CtrlCharge {
path: &'static str,
}
impl CtrlCharge {
pub(super) fn new() -> Result<Self, Box<dyn Error>> {
let path = CtrlCharge::get_battery_path()?;
Ok(CtrlCharge { path })
}
fn get_battery_path() -> Result<&'static str, std::io::Error> {
if Path::new(BAT_CHARGE_PATH).exists() {
Ok(BAT_CHARGE_PATH)
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Charge control not available",
))
}
}
pub(super) fn bat_charge_limit_reload(
&self,
config: &mut Config,
) -> Result<(), Box<dyn Error>> {
config.read();
info!("Reloaded battery charge limit");
self.set_charge_limit(config.bat_charge_limit, config)
}
pub(super) fn set_charge_limit(
&self,
limit: u8,
config: &mut Config,
) -> Result<(), Box<dyn Error>> {
if limit < 20 || limit > 100 {
warn!(
"Unable to set battery charge limit, must be between 20-100: requested {}",
limit
);
}
let mut file = OpenOptions::new()
.write(true)
.open(self.path)
.map_err(|err| {
warn!("Failed to open battery charge limit path: {:?}", err);
err
})?;
file.write_all(limit.to_string().as_bytes())
.unwrap_or_else(|err| error!("Could not write to {}, {:?}", BAT_CHARGE_PATH, err));
info!("Battery charge limit: {}", limit);
config.bat_charge_limit = limit;
config.write();
Ok(())
}
}

View File

@@ -1,33 +1,24 @@
// Return show-stopping errors, otherwise map error to a log level
use crate::{config::Config, error::RogError};
use crate::config::Config;
use log::{error, info, warn};
use std::error::Error;
use std::fs::OpenOptions;
use std::io::Write;
use std::io::{Read, Write};
use std::path::Path;
use std::process::Command;
use std::str::FromStr;
static FAN_TYPE_1_PATH: &str = "/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy";
static FAN_TYPE_2_PATH: &str = "/sys/devices/platform/asus-nb-wmi/fan_boost_mode";
static AMD_BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost";
static BAT_CHARGE_PATH: &str = "/sys/class/power_supply/BAT0/charge_control_end_threshold";
/// ROG device controller
///
/// For the GX502GW the LED setup sequence looks like:
///
/// -` LED_INIT1`
/// - `LED_INIT3`
/// - `LED_INIT4`
/// - `LED_INIT2`
/// - `LED_INIT4`
pub struct RogCore {}
pub struct CtrlFanAndCPU {
path: &'static str,
}
impl RogCore {
pub fn new(vendor: u16, product: u16) -> Self {
RogCore {}
impl CtrlFanAndCPU {
pub(super) fn new() -> Result<Self, Box<dyn Error>> {
let path = CtrlFanAndCPU::get_fan_path()?;
Ok(CtrlFanAndCPU { path })
}
fn get_fan_path() -> Result<&'static str, std::io::Error> {
@@ -43,44 +34,58 @@ impl RogCore {
}
}
pub fn fan_mode_reload(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
let path = RogCore::get_fan_path()?;
let mut file = OpenOptions::new().write(true).open(path)?;
pub(super) fn fan_mode_reload(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
let mut file = OpenOptions::new().write(true).open(self.path)?;
file.write_all(format!("{:?}\n", config.fan_mode).as_bytes())
.unwrap_or_else(|err| error!("Could not write to {}, {:?}", path, err));
.unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err));
self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?;
info!("Reloaded fan mode: {:?}", FanLevel::from(config.fan_mode));
Ok(())
}
pub fn set_fan_mode(&mut self, n: u8, config: &mut Config) -> Result<(), Box<dyn Error>> {
let path = RogCore::get_fan_path()?;
let mut fan_ctrl = OpenOptions::new().read(true).write(true).open(path)?;
pub(super) fn fan_mode_check_change(
&mut self,
config: &mut Config,
) -> Result<(), Box<dyn Error>> {
let mut file = OpenOptions::new().read(true).open(self.path)?;
let mut buf = [0u8; 1];
file.read_exact(&mut buf)?;
if let Some(num) = char::from(buf[0]).to_digit(10) {
if config.fan_mode != num as u8 {
config.fan_mode = num as u8;
config.write();
self.set_pstate_for_fan_mode(FanLevel::from(config.fan_mode), config)?;
info!(
"Fan mode was changed: {:?}",
FanLevel::from(config.fan_mode)
);
}
return Ok(());
}
let err = std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Fan-level could not be parsed",
);
Err(Box::new(err))
}
pub(super) fn set_fan_mode(
&mut self,
n: u8,
config: &mut Config,
) -> Result<(), Box<dyn Error>> {
let mut fan_ctrl = OpenOptions::new().write(true).open(self.path)?;
config.fan_mode = n;
config.write();
fan_ctrl
.write_all(format!("{:?}\n", config.fan_mode).as_bytes())
.unwrap_or_else(|err| error!("Could not write to {}, {:?}", path, err));
.unwrap_or_else(|err| error!("Could not write to {}, {:?}", self.path, err));
info!("Fan mode set to: {:?}", FanLevel::from(config.fan_mode));
self.set_pstate_for_fan_mode(FanLevel::from(n), config)?;
Ok(())
}
pub fn fan_mode_step(&mut self, config: &mut Config) -> Result<(), Box<dyn Error>> {
// re-read the config here in case a user changed the pstate settings
config.read();
let mut n = config.fan_mode;
// wrap around the step number
if n < 2 {
n += 1;
} else {
n = 0;
}
self.set_fan_mode(n, config)
}
fn set_pstate_for_fan_mode(
&self,
mode: FanLevel,
@@ -171,89 +176,10 @@ impl RogCore {
}
Ok(())
}
pub fn bat_charge_limit_reload(&self, config: &mut Config) -> Result<(), Box<dyn Error>> {
config.read();
info!("Reloaded battery charge limit");
self.set_charge_limit(config.bat_charge_limit, config)
}
pub fn set_charge_limit(&self, limit: u8, config: &mut Config) -> Result<(), Box<dyn Error>> {
if limit < 20 || limit > 100 {
warn!(
"Unable to set battery charge limit, must be between 20-100: requested {}",
limit
);
}
let mut file = OpenOptions::new()
.write(true)
.open(BAT_CHARGE_PATH)
.map_err(|err| {
warn!("Failed to open battery charge limit path: {:?}", err);
err
})?;
file.write_all(limit.to_string().as_bytes())
.unwrap_or_else(|err| error!("Could not write to {}, {:?}", BAT_CHARGE_PATH, err));
info!("Battery charge limit: {}", limit);
config.bat_charge_limit = limit;
config.write();
Ok(())
}
/// A direct call to systemd to suspend the PC.
///
/// This avoids desktop environments being required to handle it
/// (which means it works while in a TTY also)
pub fn suspend_with_systemd(&self) {
std::process::Command::new("systemctl")
.arg("suspend")
.spawn()
.map_or_else(|err| warn!("Failed to suspend: {}", err), |_| {});
}
/// A direct call to rfkill to suspend wireless devices.
///
/// This avoids desktop environments being required to handle it (which
/// means it works while in a TTY also)
pub fn toggle_airplane_mode(&self) {
match Command::new("rfkill").arg("list").output() {
Ok(output) => {
if output.status.success() {
if let Ok(out) = String::from_utf8(output.stdout) {
if out.contains(": yes") {
Command::new("rfkill")
.arg("unblock")
.arg("all")
.spawn()
.map_or_else(
|err| warn!("Could not unblock rf devices: {}", err),
|_| {},
);
} else {
Command::new("rfkill")
.arg("block")
.arg("all")
.spawn()
.map_or_else(
|err| warn!("Could not block rf devices: {}", err),
|_| {},
);
}
}
} else {
warn!("Could not list rf devices");
}
}
Err(err) => {
warn!("Could not list rf devices: {}", err);
}
}
}
}
use crate::error::RogError;
#[derive(Debug)]
pub enum FanLevel {
Normal,

View File

@@ -3,7 +3,7 @@ static LED_APPLY: [u8; 17] = [0x5d, 0xb4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
static LED_SET: [u8; 17] = [0x5d, 0xb5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
use crate::{config::Config, error::RogError};
use log::{error, info, warn};
use log::{info, warn};
use rog_client::{
aura_brightness_bytes, aura_modes::AuraModes, fancy::KeyColourArray, LED_MSG_LEN,
};
@@ -18,17 +18,18 @@ pub struct LedWriter {
impl LedWriter {
#[inline]
pub fn new(idProduct: &str, supported_modes: Vec<u8>) -> Result<Self, std::io::Error> {
pub fn new(id_product: &str, supported_modes: Vec<u8>) -> Result<Self, std::io::Error> {
let mut enumerator = udev::Enumerator::new()?;
enumerator.match_subsystem("hidraw")?;
for device in enumerator.scan_devices()? {
if let Some(parent) = device.parent_with_subsystem_devtype("usb", "usb_device")? {
if parent.attribute_value("idProduct").unwrap() == idProduct
if let Some(parent) = device
.parent_with_subsystem_devtype("usb", "usb_device")? {
if parent.attribute_value("idProduct").unwrap() == id_product
// && device.parent().unwrap().sysnum().unwrap() == 3
{
if let Some(dev_node) = device.devnode() {
info!("Using device at: {:?}", dev_node);
info!("Using device at: {:?} for LED control", dev_node);
return Ok(LedWriter {
dev_node: dev_node.to_string_lossy().to_string(),
supported_modes,

View File

@@ -1,10 +1,11 @@
use crate::{
animatrix_control::{AniMeWriter, AnimatrixCommand},
config::Config,
ctrl_anime::{AniMeWriter, AnimatrixCommand},
ctrl_charge::CtrlCharge,
ctrl_fan_cpu::CtrlFanAndCPU,
ctrl_leds::LedWriter,
dbus::dbus_create_tree,
laptops::match_laptop,
led_control::LedWriter,
rog_dbus::dbus_create_tree,
rogcore::*,
};
use dbus::{channel::Sender, nonblock::Process, nonblock::SyncConnection, tree::Signal};
@@ -34,34 +35,58 @@ pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
info!("Config loaded");
let mut rogcore = RogCore::new(laptop.usb_vendor(), laptop.usb_product());
let mut led_control = LedWriter::new(laptop.usb_product(), laptop.supported_modes().to_owned())
.map_or_else(
|err| {
error!("{}", err);
None
},
|ledwriter| {
info!("LED Writer loaded");
Some(ledwriter)
},
);
// Reload settings
rogcore
.fan_mode_reload(&mut config)
.unwrap_or_else(|err| warn!("Fan mode: {}", err));
rogcore
.bat_charge_limit_reload(&mut config)
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
let mut led_writer = LedWriter::new(
"1866",
laptop.supported_modes().to_owned(),
).map_or_else(
let mut charge_control = CtrlCharge::new().map_or_else(
|err| {
error!("{}", err);
None
},
|ledwriter| {
info!("LED Writer loaded");
info!("Charge control loaded");
Some(ledwriter)
},
);
if let Some(writer) = led_writer.as_mut() {
writer.reload_last_builtin(&mut config)
.await
.unwrap_or_else(|err| warn!("Reload settings: {}", err));
let mut fan_control = CtrlFanAndCPU::new().map_or_else(
|err| {
error!("{}", err);
None
},
|ledwriter| {
info!("Fan & CPU control loaded");
Some(ledwriter)
},
);
// Reload settings
if let Some(ctrlr) = fan_control.as_mut() {
ctrlr
.fan_mode_reload(&mut config)
.unwrap_or_else(|err| warn!("Fan mode: {}", err));
}
if let Some(ctrlr) = charge_control.as_mut() {
ctrlr
.bat_charge_limit_reload(&mut config)
.unwrap_or_else(|err| warn!("Battery charge limit: {}", err));
}
if let Some(writer) = led_control.as_mut() {
writer
.reload_last_builtin(&mut config)
.await
.unwrap_or_else(|err| warn!("Reload settings: {}", err));
}
// Set up the mutexes
@@ -133,24 +158,34 @@ pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
loop {
// TODO: MAKE SYS COMMANDS OPERATE USING CHANNEL LIKE AURA MODES
// Fan mode
if let Ok(mut lock) = fan_mode.try_lock() {
if let Some(n) = lock.take() {
let mut config = config1.lock().await;
rogcore
.set_fan_mode(n, &mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
if let Some(ctrlr) = fan_control.as_mut() {
let mut config = config1.lock().await;
ctrlr
.fan_mode_check_change(&mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
if let Ok(mut lock) = fan_mode.try_lock() {
if let Some(n) = lock.take() {
let mut config = config1.lock().await;
ctrlr
.set_fan_mode(n, &mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
}
}
}
// Charge limit
if let Ok(mut lock) = charge_limit.try_lock() {
if let Some(n) = lock.take() {
let mut config = config1.lock().await;
rogcore
.set_charge_limit(n, &mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
if let Some(ctrlr) = charge_control.as_mut() {
if let Ok(mut lock) = charge_limit.try_lock() {
if let Some(n) = lock.take() {
let mut config = config1.lock().await;
ctrlr
.set_charge_limit(n, &mut config)
.unwrap_or_else(|err| warn!("{:?}", err));
}
}
}
std::thread::sleep(std::time::Duration::from_millis(500));
tokio::time::delay_for(std::time::Duration::from_millis(500)).await;
}
});
@@ -159,30 +194,31 @@ pub async fn start_daemon() -> Result<(), Box<dyn Error>> {
connection.process_all();
while let Some(command) = aura_command_recv.recv().await {
if let Some(writer) = led_writer.as_mut() {
let mut config = config.lock().await;
match &command {
AuraModes::RGB(_) => {
writer
.do_command(command, &mut config)
.await
.unwrap_or_else(|err| warn!("{}", err));
if let Some(writer) = led_control.as_mut() {
let mut config = config.lock().await;
match &command {
AuraModes::RGB(_) => {
writer
.do_command(command, &mut config)
.await
.unwrap_or_else(|err| warn!("{}", err));
}
_ => {
let json = serde_json::to_string(&command)?;
writer
.do_command(command, &mut config)
.await
.unwrap_or_else(|err| warn!("{}", err));
connection
.send(
led_changed_signal
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
.append1(json),
)
.unwrap_or_else(|_| 0);
}
}
_ => {
let json = serde_json::to_string(&command)?;
writer
.do_command(command, &mut config)
.await
.unwrap_or_else(|err| warn!("{}", err));
connection
.send(
led_changed_signal
.msg(&DBUS_PATH.into(), &DBUS_IFACE.into())
.append1(json),
)
.unwrap_or_else(|_| 0);
}
}}
}
}
}
}

View File

@@ -11,14 +11,12 @@ pub(crate) fn match_laptop() -> LaptopBase {
let device_desc = device.device_descriptor().unwrap();
if device_desc.vendor_id() == 0x0b05 {
match device_desc.product_id() {
0x1869 | 0x1866 => return select_1866_device(device_desc.product_id()),
0x1866 => return select_1866_device("1866".to_owned()),
0x1869 => return select_1866_device("1869".to_owned()),
0x1854 => {
info!("Found GL753 or similar");
return LaptopBase {
usb_vendor: 0x0B05,
usb_product: 0x1854,
//from `lsusb -vd 0b05:1866`
led_endpoint: 0x04,
usb_product: "1854".to_string(),
supported_modes: vec![SINGLE, BREATHING, STROBE],
support_animatrix: false,
};
@@ -30,7 +28,7 @@ pub(crate) fn match_laptop() -> LaptopBase {
panic!("could not match laptop");
}
fn select_1866_device(prod: u16) -> LaptopBase {
fn select_1866_device(prod: String) -> LaptopBase {
let dmi = sysfs_class::DmiId::default();
let board_name = dmi.board_name().expect("Could not get board_name");
let prod_name = dmi.product_name().expect("Could not get board_name");
@@ -39,10 +37,7 @@ fn select_1866_device(prod: u16) -> LaptopBase {
info!("Board name: {}", board_name.trim());
let mut laptop = LaptopBase {
usb_vendor: 0x0B05,
usb_product: prod,
//from `lsusb -vd 0b05:1866`
led_endpoint: 0x04,
supported_modes: vec![],
support_animatrix: false,
};
@@ -117,22 +112,14 @@ fn select_1866_device(prod: u16) -> LaptopBase {
}
pub(super) struct LaptopBase {
usb_vendor: u16,
usb_product: u16,
led_endpoint: u8,
usb_product: String,
supported_modes: Vec<u8>,
support_animatrix: bool,
}
impl LaptopBase {
pub(super) fn led_endpoint(&self) -> u8 {
self.led_endpoint
}
pub(super) fn usb_vendor(&self) -> u16 {
self.usb_vendor
}
pub(super) fn usb_product(&self) -> u16 {
self.usb_product
pub(super) fn usb_product(&self) -> &str {
&self.usb_product
}
pub(super) fn supported_modes(&self) -> &[u8] {
&self.supported_modes

View File

@@ -1,18 +1,19 @@
#![deny(unused_must_use)]
///
mod animatrix_control;
/// Configuration loading, saving
mod config;
///
mod ctrl_anime;
///
mod ctrl_charge;
///
pub mod ctrl_fan_cpu;
///
mod ctrl_leds;
/// Start the daemon loop
pub mod daemon;
///
mod dbus;
/// Laptop matching to determine capabilities
mod laptops;
///
mod led_control;
///
mod rog_dbus;
/// The core module which allows writing to LEDs or polling the
/// laptop keyboard attached devices
pub mod rogcore;
mod error;

View File

@@ -1,5 +1,5 @@
use daemon::ctrl_fan_cpu::FanLevel;
use daemon::daemon::start_daemon;
use daemon::rogcore::FanLevel;
use gumdrop::Options;
use log::info;
use log::LevelFilter;