pyreworks_g560_driver/
lib.rs

1pub mod raw;
2
3use std::collections::HashMap;
4
5use nusb::Interface;
6use pyreworks_common::Color;
7use raw::{
8    detach_and_claim_interface,
9    ClaimInterfaceError,
10    SendCommandError
11};
12use tokio::time::{sleep, Duration};
13
14const COMMAND_RUN_TIMES: u8 = 3;
15const COMMAND_RETRY_TIMES: u8 = 3;
16const COMMAND_RETRY_INTERVAL_MS: u16 = 30;
17
18pub struct Driver {
19    interface: Interface,
20}
21
22impl Driver {
23    pub fn connect() -> Result<Driver, ClaimInterfaceError> {
24        Ok(Driver {
25            interface: detach_and_claim_interface()?,
26        })
27    }
28
29    pub async fn run(&self, commands: &[Command]) -> Result<(), SendCommandError> {
30        self.run_direct(&Command::compress(commands)).await
31    }
32
33    async fn run_direct(&self, commands: &[Command]) -> Result<(), SendCommandError> {
34        for command in commands {
35            let value = match command {
36                Command::SetColorSolid(data) => {
37                    let mut value = [0u8; 10];
38                    value[0] = data.target.addr();
39                    value[1] = command.mode_value();
40                    value[2] = data.color.r;
41                    value[3] = data.color.g;
42                    value[4] = data.color.b;
43                    value
44                },
45                Command::SetColorCycle(data) => {
46                    let mut value = [0u8; 10];
47                    value[0] = data.target.addr();
48                    value[1] = command.mode_value();
49                    value[2..7].copy_from_slice(&[0u8; 5]);
50                    value[7] = (data.rate >> 8) as u8;
51                    value[8] = data.rate as u8;
52                    value[9] = data.brightness;
53                    value
54                },
55                Command::SetColorBreathe(data) => {
56                    let mut value = [0u8; 10];
57                    value[0] = data.target.addr();
58                    value[1] = command.mode_value();
59                    value[2] = data.color.r;
60                    value[3] = data.color.g;
61                    value[4] = data.color.b;
62                    value[5] = (data.rate >> 8) as u8;
63                    value[6] = data.rate as u8;
64                    value[7] = 0x00;
65                    value[8] = data.brightness;
66                    value[9] = 0x00;
67                    value
68                },
69                Command::SetColorOff(data) => {
70                    let mut value = [0u8; 10];
71                    value[0] = data.target.addr();
72                    value[1] = command.mode_value();
73                    value[2] = 0x00;
74                    value[3] = 0x00;
75                    value[4] = 0x00;
76                    value
77                },
78            };
79
80            // Following block is meant to run `send_raw_command` multiple times with a retry mechanism.
81            let mut raw_cmd_result;
82            for _ in 0..COMMAND_RUN_TIMES {
83                for _ in 0..COMMAND_RETRY_TIMES {
84                    raw_cmd_result = raw::send_raw_command(&self.interface, &value).await;
85                    
86                    if raw_cmd_result.is_ok() {
87                        break;
88                    }
89
90                    sleep(Duration::from_millis(COMMAND_RETRY_INTERVAL_MS.into())).await;
91                }
92            }
93        }
94
95        Ok(())
96    }
97}
98
99#[derive(Clone, Copy, Eq, PartialEq, Hash)]
100pub enum Target {
101    LeftPrimary,
102    LeftSecondary,
103    RightPrimary,
104    RightSecondary,
105}
106
107impl Target {
108    pub fn all() -> [Target; 4] {
109        [
110            Target::LeftPrimary,
111            Target::LeftSecondary,
112            Target::RightPrimary,
113            Target::RightSecondary,
114        ]
115    }
116
117    pub fn lookup(name: &str) -> Box<[Target]> {
118        // TODO: This could really use some regex.
119        match name {
120            "leftfront" | "left-front" | "left_front" | "leftprimary" | "left-primary" | "left_primary"
121                => Box::new([Target::LeftPrimary]),
122            "leftback" | "left-back" | "left_back" | "leftsecondary" | "left-secondary" | "left_secondary"
123                => Box::new([Target::LeftSecondary]),
124            "rightfront" | "right-front" | "right_front" | "rightprimary" | "right-primary" | "right_primary"
125                => Box::new([Target::RightPrimary]),
126            "rightback" | "right-back" | "right_back" | "rightsecondary" | "right-secondary" | "right_secondary"
127                => Box::new([Target::RightSecondary]),
128            "left" => Box::new([Target::LeftPrimary, Target::LeftSecondary]),
129            "right" => Box::new([Target::RightPrimary, Target::RightSecondary]),
130            "front" | "primary" => Box::new([Target::LeftPrimary, Target::RightPrimary]),
131            "back" | "secondary" => Box::new([Target::LeftSecondary, Target::RightSecondary]),
132            "all" => Box::new(Target::all()),
133            _ => Box::new([]),
134        }
135    }
136    
137    fn addr(&self) -> u8 {
138        match self {
139            Target::LeftPrimary => 0x00,
140            Target::LeftSecondary => 0x02,
141            Target::RightPrimary => 0x01,
142            Target::RightSecondary => 0x03,
143        }
144    }
145}
146
147#[allow(private_interfaces)]
148#[derive(Clone)]
149pub enum Command {
150    SetColorSolid(SetColorSolidCommandData),
151    SetColorCycle(SetColorCycleCommandData),
152    SetColorBreathe(SetColorBreatheCommandData),
153    SetColorOff(SetColorOffCommandData),
154}
155
156impl Command {
157    pub fn new_color_solid(target: Target, color: Color) -> Self {
158        Self::SetColorSolid(SetColorSolidCommandData {
159            target,
160            color
161        })
162    }
163
164    pub fn new_color_cycle(target: Target, rate: u16, brightness: u8) -> Self {
165        Self::SetColorCycle(SetColorCycleCommandData {
166            target,
167            rate: num::clamp(rate, 100, 65535),
168            brightness: num::clamp(brightness, 1, 100),
169        })
170    }
171
172    pub fn new_color_breathe(target: Target, color: Color, rate: u16, brightness: u8) -> Self {
173        Self::SetColorBreathe(SetColorBreatheCommandData {
174            target,
175            color,
176            rate: num::clamp(rate, 100, 65535),
177            brightness: num::clamp(brightness, 1, 100),
178        })
179    }
180
181    pub fn new_color_off(target: Target) -> Self {
182        Self::SetColorOff(SetColorOffCommandData {
183            target
184        })
185    }
186
187    pub fn target(&self) -> Target {
188        match self {
189            Self::SetColorSolid(data) => data.target,
190            Self::SetColorCycle(data) => data.target,
191            Self::SetColorBreathe(data) => data.target,
192            Self::SetColorOff(data) => data.target,
193        }
194    }
195
196    fn mode_value(&self) -> u8 {
197        match self {
198            Self::SetColorSolid(_) => 0x01,
199            Self::SetColorCycle(_) => 0x02,
200            Self::SetColorBreathe(_) => 0x04,
201            Self::SetColorOff(_) => 0x01,
202        }
203    }
204
205    fn compress(commands: &[Command]) -> Vec<Command> {
206        let mut command_sets: HashMap<Target, Vec<Command>> = HashMap::new();
207        for target in Target::all() {
208            command_sets.insert(target, Vec::new());
209        }
210
211        for command in commands {
212            // The `unwrap()` here assumes all Targets in the command_sets HashMap have
213            // been "initialized" with empty Vecs.
214            command_sets.get_mut(&command.target()).unwrap().push(command.clone());
215        }
216
217        command_sets.into_iter()
218            .filter_map(|mut c|
219                if c.1.len() == 0 {
220                    None
221                } else {
222                    Some(c.1.remove(c.1.len()-1))
223                })
224            .collect()
225    }
226}
227
228#[derive(Clone)]
229struct SetColorSolidCommandData {
230    target: Target,
231    color: Color,
232}
233
234#[derive(Clone)]
235struct SetColorCycleCommandData {
236    target: Target,
237    rate: u16,
238    brightness: u8,
239}
240
241#[derive(Clone)]
242struct SetColorBreatheCommandData {
243    target: Target,
244    color: Color,
245    rate: u16,
246    brightness: u8,
247}
248
249#[derive(Clone)]
250struct SetColorOffCommandData {
251    target: Target,
252}