1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
use crate::{
ControllerMode, ControllerModeKind, DeviceType, Led, LedData, OpenRgbError, OpenRgbResult,
ZoneData,
client::command::Command,
data::{ModeData, ModeFlag},
protocol::{
OpenRgbProtocol,
data::{Color, ControllerData},
},
};
use super::Zone;
/// An `RGBController`, which represents a single RGB device that can be controlled.
///
/// # Example
///
/// see `examples/controller.rs` for example usage
pub struct Controller {
id: usize,
proto: OpenRgbProtocol,
data: ControllerData,
}
impl PartialEq for Controller {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.data == other.data
}
}
impl Eq for Controller {}
impl std::fmt::Debug for Controller {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Controller")
.field("id", &self.id)
.field("name", &self.name())
.field("num_leds", &self.num_leds())
.field("modes", &self.modes().len())
.finish()
}
}
impl Controller {
pub(crate) fn new(id: usize, proto: OpenRgbProtocol, data: ControllerData) -> Self {
Self { id, proto, data }
}
pub(crate) fn proto(&self) -> &OpenRgbProtocol {
&self.proto
}
/// Returns the ID of this controller.
pub fn id(&self) -> usize {
self.id
}
delegate::delegate! {
to self.data {
/// Returns the name of this controller.
pub fn name(&self) -> &str;
/// Returns the type of this controller.
pub fn device_type(&self) -> DeviceType;
/// Returns the vendor of this controller.
pub fn vendor(&self) -> &str;
/// Returns a description for this controller.
pub fn description(&self) -> &str;
/// Returns the version of this controller.
pub fn version(&self) -> &str;
/// Returns the serial number of this controller.
pub fn serial(&self) -> &str;
/// Returns the location of this controller.
pub fn location(&self) -> &str;
/// Returns the currently set colors of this controller.
///
/// These have to be manually refreshed using [`Self::sync_controller_data()`].
pub fn colors(&self) -> &[Color];
/// Returns the number of LEDs in this controller.
pub fn num_leds(&self) -> usize;
/// Returns the modes supported by this controller.
///
/// [`Self::set_controllable_mode()`] will set the controller to the mode named "direct"
pub fn modes(&self) -> &[ModeData];
/// Returns the LEDs in this controller
// #[expect(unused, reason = "Api not finalised yet")]
// pub(crate) fn leds(&self) -> &[LedData];
pub(crate) fn zones(&self) -> &[ZoneData];
#[call(leds)]
pub(crate) fn led_data(&self) -> &[LedData];
}
}
/// Initialises a controller by setting it to a controllable mode.
/// This function also changes the LEDs to a rainbow, so you can see if it worked.
pub async fn init(&self) -> OpenRgbResult<()> {
const RAINBOW_COLORS: [Color; 7] = [
Color::new(255, 0, 0), // Red
Color::new(255, 127, 0), // Orange
Color::new(255, 255, 0), // Yellow
Color::new(0, 255, 0), // Green
Color::new(0, 0, 255), // Blue
Color::new(75, 0, 130), // Indigo
Color::new(148, 0, 211), // Violet
];
self.set_controllable_mode().await?;
let colors = (0..self.num_leds())
.map(|i| i * RAINBOW_COLORS.len() / self.num_leds())
.map(|i| RAINBOW_COLORS[i]);
self.set_leds(colors).await?;
Ok(())
}
/// Returns the active mode of this controller.
pub fn active_mode(&self) -> ControllerMode<'_> {
let mode = self.data.active_mode().expect(
"OpenRGB controller has no active mode. Create an issue for this if you encouter this.",
);
ControllerMode::new(mode, true)
}
/// Returns an iterator over all available modes in this controller.
pub fn mode_iter(&self) -> impl Iterator<Item = ControllerMode<'_>> {
let active_mode = self.data.active_mode().expect(
"OpenRGB controller has no active mode. Create an issue for this if you encouter this.",
);
self.data
.modes()
.iter()
.map(|m| ControllerMode::new(m, active_mode.id() == m.id()))
}
/// Sets this controller to a controllable mode.
pub async fn set_controllable_mode(&self) -> OpenRgbResult<()> {
let mode = self
.mode_iter()
.find(|m| m.kind() == ControllerModeKind::Direct)
.ok_or_else(|| OpenRgbError::ProtocolError("No controllable mode found".to_owned()))?;
// set max brightness if possible
if let Ok(b) = mode.builder().set_max_brightness() {
b.execute(self).await?;
}
tracing::debug!("Setting {} to {} mode", self.name(), mode.name());
self.proto
.update_mode(self.id as u32, mode.into_data())
.await?;
Ok(())
}
pub(crate) async fn update_mode(&self, mode: &ModeData) -> OpenRgbResult<()> {
self.proto.update_mode(self.id as u32, mode).await
}
/// Returns the zone with the given `zone_id`.
pub fn get_zone(&self, zone_id: usize) -> OpenRgbResult<Zone<'_>> {
let zone_data = self.zones().get(zone_id).ok_or_else(|| {
OpenRgbError::CommandError(format!("Zone {zone_id} not found for {}", self.name()))
})?;
let zone = Zone::new(self, zone_data);
Ok(zone)
}
/// Returns an iterator over all available zones in this controller.
pub fn get_all_zones(&self) -> impl Iterator<Item = Zone<'_>> {
self.zones().iter().map(|z| Zone::new(self, z))
}
/// Sets a single LED to the given `color`.
///
/// When doing many writes in rapid succession, it is recommended to use the [`Self::cmd()`] method instead.
pub async fn set_led<C: Into<Color>>(&self, led: usize, color: C) -> OpenRgbResult<()> {
self.proto
.update_led(self.id as u32, led as i32, &color.into())
.await
}
/// Sets all LEDs of this controller to a given `color`.
pub async fn set_all_leds<C: Into<Color>>(&self, color: C) -> OpenRgbResult<()> {
let color = color.into();
let colors = (0..self.num_leds()).map(|_| color);
self.set_leds(colors).await?;
Ok(())
}
/// Sets the LEDs of this controller to the given `colors`.
pub async fn set_leds<C: Into<Color>>(
&self,
colors: impl IntoIterator<Item = C>,
) -> OpenRgbResult<()> {
let color_v = colors.into_iter().map(Into::into).collect::<Vec<_>>();
self.proto.update_leds(self.id as u32, &color_v).await
}
/// Sets the LEDs of a specific zone to the given `colors`.
pub async fn set_zone_leds<C: Into<Color>>(
&self,
zone_id: usize,
colors: impl IntoIterator<Item = C>,
) -> OpenRgbResult<()> {
let color_v = colors.into_iter().map(Into::into).collect::<Vec<_>>();
self.proto
.update_zone_leds(self.id as u32, zone_id as u32, &color_v)
.await
}
/// Turns off all LEDs of this controller.
pub async fn turn_off_leds(&self) -> OpenRgbResult<()> {
self.set_controllable_mode().await?;
self.set_all_leds(Color { r: 0, g: 0, b: 0 }).await
}
/// Returns an iterator over the Leds of this controller.
pub fn led_iter(&self) -> impl Iterator<Item = Led<'_>> {
// assumption: controller_data led_data and colors agree
self.led_data()
.iter()
.enumerate()
.map(move |(id, _)| Led::new(id, self))
}
/// Creates a [`Command`] for this controller.
///
/// Controller LEDs can be updated in three ways:
/// * per led: `self.set_led()`
/// * per zone: `self.set_zone_leds()`
/// * all at once: `self.set_leds()`
///
/// From my testing, the most efficient way is to always update all LEDs at once.
/// The `Command` API lets you build a command using updates to individual LEDs, zones or segments
/// and then executes them as a single `set_led()` call.
///
/// # Example
/// ```no_run
/// # use openrgb2::{OpenRgbClient, OpenRgbResult, Color};
/// // let's say we have a controller with 5 LEDs
/// # async fn example() -> OpenRgbResult<()> {
/// let mut client = OpenRgbClient::connect().await?;
/// let controller = client.get_controller(0).await?;
/// // direct write
/// controller.set_leds([Color::new(255, 0, 0); 5]).await?;
/// // equivalent with command
/// let mut cmd = controller.cmd();
/// cmd.set_led(0, Color::new(255, 0, 0))?;
/// cmd.set_led(2, Color::new(255, 0, 0))?; // order doesn't matter
/// cmd.set_led(4, Color::new(255, 0, 0))?;
/// cmd.set_led(1, Color::new(255, 0, 0))?;
/// cmd.set_led(5, Color::new(255, 0, 0))?;
/// // this is just a single api call
/// cmd.execute().await
/// # }
/// ```
///
/// This is especially useful for devices with multiple zones that should animate separately.
#[must_use]
pub fn cmd(&self) -> Command<'_> {
Command::new(self)
}
/// Creates a new [`Command`] for this controller
/// and sets the LED colors using the provided closure.
#[must_use]
pub fn cmd_with_leds<'a, F>(&'a self, led_clr: F) -> Command<'a>
where
F: Fn(Led<'a>) -> Color,
{
let mut cmd = self.cmd();
for led in self.led_iter() {
// this cannot fail, since we know the led id will be in bounds
cmd.set_led(led.id(), led_clr(led))
.expect("Led index incorrect");
}
cmd
}
pub(crate) fn get_zone_led_offset(&self, zone_id: usize) -> OpenRgbResult<usize> {
if zone_id >= self.zones().len() {
return Err(OpenRgbError::ProtocolError(format!(
"zone {zone_id} not found in controller {}",
self.id
)));
}
let offset = self
.zones()
.iter()
.filter(|z| z.id < zone_id)
.map(|z| z.leds_count as usize)
.sum::<usize>();
Ok(offset)
}
/// Fetches controller data again. This updates the state of the controller data.
///
/// Currently this has to be called manually.
pub async fn sync_controller_data(&mut self) -> OpenRgbResult<()> {
let data = self.proto.get_controller(self.id as u32).await?;
self.data = data;
Ok(())
}
/// Saves the current mode of this controller to the flash memory of the controller.
///
/// # Important
///
/// Using this frequently can cause wear on the flash memory, use this sparingly.
pub async fn save_mode(&self) -> OpenRgbResult<()> {
let active_mode = self.active_mode();
if !active_mode.flags().contains(ModeFlag::ManualSave) {
return Err(OpenRgbError::CommandError(format!(
"Controller {} mode {} cannot be saved",
self.name(),
active_mode.name()
)));
}
self.proto
.save_mode(self.id as u32, active_mode.into_data())
.await
}
/// Clears all segments of this controller.
pub async fn clear_segments(&self) -> OpenRgbResult<()> {
self.proto.clear_segments(self.id as u32).await
}
}
#[cfg(test)]
mod tests {
use crate::OpenRgbClient;
use super::*;
#[tokio::test]
#[ignore = "can only test with openrgb running"]
async fn test_update_leds() -> OpenRgbResult<()> {
let client = OpenRgbClient::connect().await?;
let controller = client.get_controller(0).await?;
controller.set_controllable_mode().await?;
controller.set_leds([Color::new(255, 0, 50); 96]).await?;
Ok(())
}
#[tokio::test]
#[ignore = "can only test with openrgb running"]
async fn test_cmd() -> OpenRgbResult<()> {
let client = OpenRgbClient::connect().await?;
let controller = client.get_controller(5).await?;
controller.set_controllable_mode().await?;
println!("controller: {0:#?}", controller.data.led_alt_names());
let mut cmd = controller.cmd();
cmd.set_led(19, Color::new(255, 0, 255))?;
cmd.set_zone_leds(0, vec![Color::new(255, 255, 0); 19])?;
cmd.set_zone_leds(1, vec![Color::new(0, 255, 255); 75])?;
cmd.execute().await?;
Ok(())
}
}