SCSI support: ROG Arion external drive LED control

This commit is contained in:
Luke D. Jones
2024-12-21 20:35:51 +13:00
parent 19ffcf3376
commit 0f2d89858e
24 changed files with 1393 additions and 172 deletions

View 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
View 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
View 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
View 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
}