mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
SCSI support: ROG Arion external drive LED control
This commit is contained in:
398
rog-scsi/src/builtin_modes.rs
Normal file
398
rog-scsi/src/builtin_modes.rs
Normal file
@@ -0,0 +1,398 @@
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
#[cfg(feature = "dbus")]
|
||||
use zbus::zvariant::{OwnedValue, Type, Value};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::scsi::{apply_task, dir_task, mode_task, rgb_task, save_task, speed_task};
|
||||
|
||||
#[typeshare]
|
||||
#[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy, Deserialize, Serialize)]
|
||||
pub struct Colour {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl Default for Colour {
|
||||
fn default() -> Self {
|
||||
Colour { r: 166, g: 0, b: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Colour {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s.len() < 6 {
|
||||
return Err(Error::ParseColour);
|
||||
}
|
||||
let r = u8::from_str_radix(&s[0..2], 16).or(Err(Error::ParseColour))?;
|
||||
let g = u8::from_str_radix(&s[2..4], 16).or(Err(Error::ParseColour))?;
|
||||
let b = u8::from_str_radix(&s[4..6], 16).or(Err(Error::ParseColour))?;
|
||||
Ok(Colour { r, g, b })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8; 3]> for Colour {
|
||||
fn from(c: &[u8; 3]) -> Self {
|
||||
Self {
|
||||
r: c[0],
|
||||
g: c[1],
|
||||
b: c[2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Colour> for [u8; 3] {
|
||||
fn from(c: Colour) -> Self {
|
||||
[c.r, c.b, c.g]
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[cfg_attr(
|
||||
feature = "dbus",
|
||||
derive(Type, Value, OwnedValue),
|
||||
zvariant(signature = "u")
|
||||
)]
|
||||
pub enum Direction {
|
||||
#[default]
|
||||
Forward = 0,
|
||||
Reverse = 1,
|
||||
}
|
||||
|
||||
impl FromStr for Direction {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.as_str() {
|
||||
"forward" => Ok(Direction::Forward),
|
||||
"reverse" => Ok(Direction::Reverse),
|
||||
_ => Err(Error::ParseSpeed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Direction {
|
||||
fn from(dir: u8) -> Self {
|
||||
match dir {
|
||||
1 => Direction::Reverse,
|
||||
_ => Direction::Forward,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Direction> for u8 {
|
||||
fn from(d: Direction) -> Self {
|
||||
d as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[cfg_attr(
|
||||
feature = "dbus",
|
||||
derive(Type, Value, OwnedValue),
|
||||
zvariant(signature = "s")
|
||||
)]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Speed {
|
||||
Slowest = 4,
|
||||
Slow = 3,
|
||||
#[default]
|
||||
Med = 2,
|
||||
Fast = 1,
|
||||
Fastest = 0,
|
||||
}
|
||||
|
||||
impl FromStr for Speed {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
match s.as_str() {
|
||||
"slowest" => Ok(Speed::Slowest),
|
||||
"slow" => Ok(Speed::Slow),
|
||||
"med" => Ok(Speed::Med),
|
||||
"fast" => Ok(Speed::Fast),
|
||||
"fastest" => Ok(Speed::Fastest),
|
||||
_ => Err(Error::ParseSpeed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Speed> for u8 {
|
||||
fn from(s: Speed) -> u8 {
|
||||
match s {
|
||||
Speed::Slowest => 4,
|
||||
Speed::Slow => 3,
|
||||
Speed::Med => 2,
|
||||
Speed::Fast => 1,
|
||||
Speed::Fastest => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Speed {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
4 => Self::Slowest,
|
||||
3 => Self::Slow,
|
||||
1 => Self::Fast,
|
||||
0 => Self::Fastest,
|
||||
_ => Self::Med,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum of modes that convert to the actual number required by a USB HID packet
|
||||
#[typeshare]
|
||||
#[cfg_attr(
|
||||
feature = "dbus",
|
||||
derive(Type, Value, OwnedValue),
|
||||
zvariant(signature = "u")
|
||||
)]
|
||||
#[derive(
|
||||
Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Deserialize, Serialize,
|
||||
)]
|
||||
pub enum AuraMode {
|
||||
Off = 0,
|
||||
#[default]
|
||||
Static = 1,
|
||||
Breathe = 2,
|
||||
Flashing = 3,
|
||||
RainbowCycle = 4,
|
||||
RainbowWave = 5,
|
||||
RainbowCycleBreathe = 6,
|
||||
ChaseFade = 7,
|
||||
RainbowCycleChaseFade = 8,
|
||||
Chase = 9,
|
||||
RainbowCycleChase = 10,
|
||||
RainbowCycleWave = 11,
|
||||
RainbowPulseChase = 12,
|
||||
RandomFlicker = 13,
|
||||
DoubleFade = 14,
|
||||
}
|
||||
|
||||
impl AuraMode {
|
||||
pub fn list() -> [String; 15] {
|
||||
[
|
||||
AuraMode::Off.to_string(),
|
||||
AuraMode::Static.to_string(),
|
||||
AuraMode::Breathe.to_string(),
|
||||
AuraMode::Flashing.to_string(),
|
||||
AuraMode::RainbowCycle.to_string(),
|
||||
AuraMode::RainbowWave.to_string(),
|
||||
AuraMode::RainbowCycleBreathe.to_string(),
|
||||
AuraMode::ChaseFade.to_string(),
|
||||
AuraMode::RainbowCycleChaseFade.to_string(),
|
||||
AuraMode::Chase.to_string(),
|
||||
AuraMode::RainbowCycleChase.to_string(),
|
||||
AuraMode::RainbowCycleWave.to_string(),
|
||||
AuraMode::RainbowPulseChase.to_string(),
|
||||
AuraMode::RandomFlicker.to_string(),
|
||||
AuraMode::DoubleFade.to_string(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AuraMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", <&str>::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuraMode> for String {
|
||||
fn from(mode: AuraMode) -> Self {
|
||||
<&str>::from(&mode).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuraMode> for &str {
|
||||
fn from(mode: &AuraMode) -> Self {
|
||||
match mode {
|
||||
AuraMode::Off => "Off",
|
||||
AuraMode::Static => "Static",
|
||||
AuraMode::Breathe => "Breathe",
|
||||
AuraMode::RainbowCycle => "RainbowCycle",
|
||||
AuraMode::RainbowWave => "RainbowWave",
|
||||
AuraMode::Flashing => "Flashing",
|
||||
AuraMode::RainbowCycleBreathe => "RainbowCycleBreathe",
|
||||
AuraMode::ChaseFade => "ChaseFade",
|
||||
AuraMode::RainbowCycleChaseFade => "RainbowCycleChaseFade",
|
||||
AuraMode::Chase => "Chase",
|
||||
AuraMode::RainbowCycleChase => "RainbowCycleChase",
|
||||
AuraMode::RainbowCycleWave => "RainbowCycleWave",
|
||||
AuraMode::RainbowPulseChase => "RainbowPulseChase",
|
||||
AuraMode::RandomFlicker => "RandomFlicker",
|
||||
AuraMode::DoubleFade => "DoubleFade",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AuraMode {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(mode: &str) -> Result<Self, Self::Err> {
|
||||
match mode {
|
||||
"Off" => Ok(Self::Off),
|
||||
"Static" => Ok(Self::Static),
|
||||
"Breathe" => Ok(Self::Breathe),
|
||||
"RainbowCycle" => Ok(Self::RainbowCycle),
|
||||
"RainbowWave" => Ok(Self::RainbowWave),
|
||||
"Flashing" => Ok(Self::Flashing),
|
||||
"RainbowCycleBreathe" => Ok(Self::RainbowCycleBreathe),
|
||||
"ChaseFade" => Ok(Self::ChaseFade),
|
||||
"RainbowCycleChaseFade" => Ok(Self::RainbowCycleChaseFade),
|
||||
"Chase" => Ok(Self::Chase),
|
||||
"RainbowCycleChase" => Ok(Self::RainbowCycleChase),
|
||||
"RainbowCycleWave" => Ok(Self::RainbowCycleWave),
|
||||
"RainbowPulseChase" => Ok(Self::RainbowPulseChase),
|
||||
"RandomFlicker" => Ok(Self::RandomFlicker),
|
||||
"DoubleFade" => Ok(Self::DoubleFade),
|
||||
_ => Err(Error::ParseMode),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AuraMode {
|
||||
fn from(mode: &str) -> Self {
|
||||
AuraMode::from_str(mode).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for AuraMode {
|
||||
fn from(mode: u8) -> Self {
|
||||
match mode {
|
||||
0 => Self::Off,
|
||||
1 => Self::Static,
|
||||
2 => Self::Breathe,
|
||||
3 => Self::Flashing,
|
||||
4 => Self::RainbowCycle,
|
||||
5 => Self::RainbowWave,
|
||||
6 => Self::RainbowCycleBreathe,
|
||||
7 => Self::ChaseFade,
|
||||
8 => Self::RainbowCycleChaseFade,
|
||||
9 => Self::Chase,
|
||||
10 => Self::RainbowCycleChase,
|
||||
11 => Self::RainbowCycleWave,
|
||||
12 => Self::RainbowPulseChase,
|
||||
13 => Self::RandomFlicker,
|
||||
14 => Self::DoubleFade,
|
||||
_ => Self::Static,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuraEffect> for AuraMode {
|
||||
fn from(value: AuraEffect) -> Self {
|
||||
value.mode
|
||||
}
|
||||
}
|
||||
|
||||
/// Default factory modes structure.
|
||||
#[typeshare]
|
||||
#[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))]
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct AuraEffect {
|
||||
/// The effect type
|
||||
pub mode: AuraMode,
|
||||
/// One of three speeds for modes that support speed (most that animate)
|
||||
pub speed: Speed,
|
||||
/// Up, down, left, right. Only Rainbow mode seems to use this
|
||||
pub direction: Direction,
|
||||
/// Primary colour for all modes
|
||||
pub colour1: Colour,
|
||||
/// Secondary colour in some modes like Breathing or Stars
|
||||
pub colour2: Colour,
|
||||
pub colour3: Colour,
|
||||
pub colour4: Colour,
|
||||
}
|
||||
|
||||
impl AuraEffect {
|
||||
pub fn mode(&self) -> &AuraMode {
|
||||
&self.mode
|
||||
}
|
||||
|
||||
pub fn mode_name(&self) -> &str {
|
||||
<&str>::from(&self.mode)
|
||||
}
|
||||
|
||||
pub fn mode_num(&self) -> u8 {
|
||||
self.mode as u8
|
||||
}
|
||||
|
||||
pub fn default_with_mode(mode: AuraMode) -> Self {
|
||||
Self {
|
||||
mode,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AuraEffect {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: AuraMode::Static,
|
||||
colour1: Colour { r: 166, g: 0, b: 0 },
|
||||
colour2: Colour { r: 0, g: 0, b: 0 },
|
||||
colour3: Colour { r: 166, g: 0, b: 0 },
|
||||
colour4: Colour { r: 0, g: 0, b: 0 },
|
||||
speed: Speed::Med,
|
||||
direction: Direction::Forward,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AuraEffect {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "AuraEffect {{")?;
|
||||
writeln!(f, " mode: {}", self.mode())?;
|
||||
writeln!(f, " speed: {:?}", self.speed)?;
|
||||
writeln!(f, " direction: {:?}", self.direction)?;
|
||||
writeln!(f, " colour1: {:?}", self.colour1)?;
|
||||
writeln!(f, " colour2: {:?}", self.colour2)?;
|
||||
writeln!(f, " colour3: {:?}", self.colour3)?;
|
||||
writeln!(f, " colour4: {:?}", self.colour4)?;
|
||||
writeln!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuraEffect> for Vec<sg::Task> {
|
||||
fn from(effect: &AuraEffect) -> Self {
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
tasks.append(&mut vec![
|
||||
mode_task(effect.mode as u8),
|
||||
rgb_task(0, &effect.colour1.into()),
|
||||
rgb_task(1, &effect.colour2.into()),
|
||||
rgb_task(2, &effect.colour3.into()),
|
||||
rgb_task(3, &effect.colour4.into()),
|
||||
]);
|
||||
|
||||
if !matches!(effect.mode, AuraMode::Static | AuraMode::Off) {
|
||||
tasks.push(speed_task(effect.speed as u8));
|
||||
}
|
||||
if matches!(
|
||||
effect.mode,
|
||||
AuraMode::RainbowWave
|
||||
| AuraMode::ChaseFade
|
||||
| AuraMode::RainbowCycleChaseFade
|
||||
| AuraMode::Chase
|
||||
| AuraMode::RainbowCycleChase
|
||||
| AuraMode::RainbowCycleWave
|
||||
| AuraMode::RainbowPulseChase
|
||||
) {
|
||||
tasks.push(dir_task(effect.direction as u8));
|
||||
}
|
||||
|
||||
tasks.append(&mut vec![apply_task(), save_task()]);
|
||||
tasks
|
||||
}
|
||||
}
|
||||
41
rog-scsi/src/error.rs
Normal file
41
rog-scsi/src/error.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::{error, fmt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
ParseMode,
|
||||
ParseColour,
|
||||
ParseSpeed,
|
||||
ParseDirection,
|
||||
IoPath(String, std::io::Error),
|
||||
Ron(ron::Error),
|
||||
RonParse(ron::error::SpannedError),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::ParseColour => write!(f, "Could not parse colour"),
|
||||
Error::ParseSpeed => write!(f, "Could not parse speed"),
|
||||
Error::ParseDirection => write!(f, "Could not parse direction"),
|
||||
Error::ParseMode => write!(f, "Could not parse mode"),
|
||||
Error::IoPath(path, io) => write!(f, "IO Error: {path}, {io}"),
|
||||
Error::Ron(e) => write!(f, "RON Parse Error: {e}"),
|
||||
Error::RonParse(e) => write!(f, "RON Parse Error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl From<ron::Error> for Error {
|
||||
fn from(e: ron::Error) -> Self {
|
||||
Self::Ron(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ron::error::SpannedError> for Error {
|
||||
fn from(e: ron::error::SpannedError) -> Self {
|
||||
Self::RonParse(e)
|
||||
}
|
||||
}
|
||||
48
rog-scsi/src/lib.rs
Normal file
48
rog-scsi/src/lib.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
mod builtin_modes;
|
||||
mod error;
|
||||
mod scsi;
|
||||
|
||||
pub use builtin_modes::*;
|
||||
pub use error::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use sg::{Device, Task};
|
||||
|
||||
pub const PROD_SCSI_ARION: &str = "1932";
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum ScsiType {
|
||||
Arion,
|
||||
#[default]
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl ScsiType {
|
||||
pub const fn prod_id_str(&self) -> &str {
|
||||
match self {
|
||||
ScsiType::Arion => PROD_SCSI_ARION,
|
||||
ScsiType::Unsupported => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ScsiType {
|
||||
fn from(s: &str) -> Self {
|
||||
match s.to_lowercase().as_str() {
|
||||
PROD_SCSI_ARION | "0x1932" => Self::Arion,
|
||||
_ => Self::Unsupported,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScsiType> for &str {
|
||||
fn from(s: ScsiType) -> Self {
|
||||
match s {
|
||||
ScsiType::Arion => PROD_SCSI_ARION,
|
||||
ScsiType::Unsupported => "Unsupported",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_device(path: &str) -> Result<Device, std::io::Error> {
|
||||
Device::open(path)
|
||||
}
|
||||
80
rog-scsi/src/scsi.rs
Normal file
80
rog-scsi/src/scsi.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
extern crate sg;
|
||||
|
||||
pub use sg::Task;
|
||||
|
||||
static ENE_APPLY_VAL: u8 = 0x01; // Value for Apply Changes Register
|
||||
static ENE_SAVE_VAL: u8 = 0xaa;
|
||||
|
||||
static ENE_REG_MODE: u32 = 0x8021; // Mode Selection Register
|
||||
static ENE_REG_SPEED: u32 = 0x8022; // Speed Control Register
|
||||
static ENE_REG_DIRECTION: u32 = 0x8023; // Direction Control Register
|
||||
|
||||
static ENE_REG_APPLY: u32 = 0x80a0;
|
||||
static _ENE_REG_COLORS_DIRECT_V2: u32 = 0x8100; // to read the colurs
|
||||
static ENE_REG_COLORS_EFFECT_V2: u32 = 0x8160;
|
||||
|
||||
fn data(reg: u32, arg_count: u8) -> [u8; 16] {
|
||||
let mut cdb = [0u8; 16];
|
||||
cdb[0] = 0xec;
|
||||
cdb[1] = 0x41;
|
||||
cdb[2] = 0x53;
|
||||
cdb[3] = ((reg >> 8) & 0x00ff) as u8;
|
||||
cdb[4] = (reg & 0x00ff) as u8;
|
||||
cdb[5] = 0x00;
|
||||
cdb[6] = 0x00;
|
||||
cdb[7] = 0x00;
|
||||
cdb[8] = 0x00;
|
||||
cdb[9] = 0x00;
|
||||
cdb[10] = 0x00;
|
||||
cdb[11] = 0x00;
|
||||
cdb[12] = 0x00;
|
||||
cdb[13] = arg_count; // how many u8 in data packet
|
||||
cdb[14] = 0x00;
|
||||
cdb[15] = 0x00;
|
||||
cdb
|
||||
}
|
||||
|
||||
pub(crate) fn rgb_task(led: u32, rgb: &[u8; 3]) -> Task {
|
||||
let mut task = Task::new();
|
||||
task.set_cdb(data(led * 3 + ENE_REG_COLORS_EFFECT_V2, 3).as_slice());
|
||||
task.set_data(rgb, sg::Direction::ToDevice);
|
||||
task
|
||||
}
|
||||
|
||||
/// 0-13
|
||||
pub(crate) fn mode_task(mode: u8) -> Task {
|
||||
let mut task = Task::new();
|
||||
task.set_cdb(data(ENE_REG_MODE, 1).as_slice());
|
||||
task.set_data(&[mode.min(13)], sg::Direction::ToDevice);
|
||||
task
|
||||
}
|
||||
|
||||
/// 0-4, fast to slow
|
||||
pub(crate) fn speed_task(speed: u8) -> Task {
|
||||
let mut task = Task::new();
|
||||
task.set_cdb(data(ENE_REG_SPEED, 1).as_slice());
|
||||
task.set_data(&[speed.min(4)], sg::Direction::ToDevice);
|
||||
task
|
||||
}
|
||||
|
||||
/// 0 = forward, 1 = backward
|
||||
pub(crate) fn dir_task(mode: u8) -> Task {
|
||||
let mut task = Task::new();
|
||||
task.set_cdb(data(ENE_REG_DIRECTION, 1).as_slice());
|
||||
task.set_data(&[mode.min(1)], sg::Direction::ToDevice);
|
||||
task
|
||||
}
|
||||
|
||||
pub(crate) fn apply_task() -> Task {
|
||||
let mut task = Task::new();
|
||||
task.set_cdb(data(ENE_REG_APPLY, 1).as_slice());
|
||||
task.set_data(&[ENE_APPLY_VAL], sg::Direction::ToDevice);
|
||||
task
|
||||
}
|
||||
|
||||
pub(crate) fn save_task() -> Task {
|
||||
let mut task = Task::new();
|
||||
task.set_cdb(data(ENE_REG_APPLY, 1).as_slice());
|
||||
task.set_data(&[ENE_SAVE_VAL], sg::Direction::ToDevice);
|
||||
task
|
||||
}
|
||||
Reference in New Issue
Block a user