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